fraud*monitoring*and* prevengon*in*banking* systems*(and ...infigo*fraud*monitoring*!...
TRANSCRIPT
Copyright © 2014 Splunk Inc.
Bojan Zdrnja CTO, INFIGO IS
Fraud Monitoring and PrevenGon in Banking Systems (and Beyond)
Agenda
! Fraud – Trends in banking fraud… and beyond – How Splunk fits this game?
! INFIGO Fraud Monitoring – Retrieving the data – Processing/analyzing the data – PresenGng the data
! INFIGO ReporGng for Splunk ! Q&A
2
Trends in Banking Fraud … and Beyond
! Financial industry has been prone to fraud since the beginning – Most damages come from internal fraudsters (employees)
! AZacks against Internet banking increasingly “popular” – AZacker’s switch targets from banks to end-‐users
! Significant number of machines infected with advanced Internet banking malware
! Fraud is present everywhere – Can you think of a possible fraud in your organizaGon?
ê Are you sure no one exploited it before?
3
A Confused Client Calls In …
! Very typical of ZeuS-‐like Trojan – Man-‐in-‐the-‐Browser aZack
4
So How Do We Detect Fraud?
! It is all about the rules – StaGc rules – Dynamic rules (staGsGcal based)
! Search for paZerns in data – What is the average amount of money transferred by this user? – Has this user ever transferred money to this account before? – Is this a new account, that was opened recently, and that has an incoming
transacGon higher than $X USD? ê Hint: this is money laundering
5
We Want Splunk To Do the Following…
! Reduce Gme for fraud detecGon ! Reduce Gme for fraud invesGgaGon ! Reduce losses incurred by fraud ! Reduce reputaGon risk ! Increase customer protecGon ! Cover any transacGon
– Why stay in the financial industry only? – Fraudsters always have specific paZerns
6
INFIGO Fraud Monitoring ! Based on (doh, of course) Splunk
– Most fraud monitoring products today use SQL databases to store data ê This does not really scale ê … and it is not as flexible as we need it to be
! Developed within 2 years ! Splunk is used as the plalorm on which the product has been built
– Ensures pracGcally unlimited scalability – Unlimited data processing capability – No “structured data only” requirements – Unlimited historic data retenGon
ê We need this to calculate staGsGcs – Built-‐in clustering ensures high availability
7
INFIGO Fraud Monitoring
! What does it look like?
It’s DEMO Gme ….
8
Architecture
9
Minimizing the Number of Splunk Servers
! More servers, more effort to keep them up to date ! Installed search head instances on indexers
– Minimizes the number of servers – Only one search head is acGve (DNS points to the acGve search head) – Use scripts (rsync) to keep the data up-‐to-‐date between mulGple nodes – Master script runs on both nodes, checks which one is acGve and sets up the
synchronizing process accordingly
! Simple shell scripts do wonders ! Available at hZps://github.com/bojanisc/splunk-‐rsync
10
Data Sources
! The system needs to deal with mulGple (dozens) of different data sources
! Core banking systems typically rely on databases – Expect to see hundreds of tables with millions of rows
ê Not uncommon to see very badly designed tables with non-‐exisGng indexes
! Front end servers can be various – Reverse proxies/web servers for access logs – ApplicaGon servers for user/acGon idenGficaGon – Databases for transacGon details
! Need a way to correlate all that informaGon
11
Data Sources
! What have we used ! Splunk Universal Forwarder
– Used for all plain text data logs – If there are binary logs, create a connector uGlity to process them before
shipping to Splunk
! DB Connect – Used to retrieve data from databases – Queries can be extremely complex
ê But it is all SQL at the end of the day
12
Typical DB Connect ConfiguraGon [dbmon-‐tail://ORACLE/TRANSACTIONS] host = aixserver index = transacGons
interval = auto output.format = kv output.Gmestamp = 0 query = SELECT DISTINCT TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),1,4)) || '-‐' || TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),5,2)) || '-‐' || TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),7,2)) || ' ' || TO_CHAR (TO_DATE(MOD(TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),2,2))*3600 + TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),4,2))*60 + TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),6,2 )),86400), 'SSSSS'),'HH24:MI:SS') || '.' || SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),8,2) || '0' AS TIME, … AND COUNTER>2218573342 {{AND COUNTER > ?}} tail.rising.column = COUNTER
13
Ugly Gmestamp: DATECOLUMN = 20140611 TIMECOLUMN = 7313504
Typical DB Connect ConfiguraGon [dbmon-‐tail://ORACLE/TRANSACTIONS] host = aixserver index = transacGons
interval = auto output.format = kv output.Gmestamp = 0 query = SELECT DISTINCT TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),1,4)) || '-‐' || TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),5,2)) || '-‐' || TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),7,2)) || ' ' || TO_CHAR (TO_DATE(MOD(TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),2,2))*3600 + TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),4,2))*60 + TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),6,2 )),86400), 'SSSSS'),'HH24:MI:SS') || '.' || SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),8,2) || '0' AS TIME, … AND COUNTER>2218573342 {{AND COUNTER > ?}} tail.rising.column = COUNTER
14
The table has billions of rows, make sure DB Connect does not pull them all on the iniGal run
Typical DB Connect ConfiguraGon [dbmon-‐tail://ORACLE/TRANSACTIONS] host = aixserver index = transacGons
interval = auto output.format = kv output.Gmestamp = 0 query = SELECT DISTINCT TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),1,4)) || '-‐' || TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),5,2)) || '-‐' || TO_CHAR(SUBSTR(TO_CHAR(a.DATECOLUMN),7,2)) || ' ' || TO_CHAR (TO_DATE(MOD(TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),2,2))*3600 + TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),4,2))*60 + TO_NUMBER(SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),6,2 )),86400), 'SSSSS'),'HH24:MI:SS') || '.' || SUBSTR(TO_CHAR(a.HOURCOLUMN,'00000000'),8,2) || '0' AS TIME, … AND COUNTER>2218573342 {{AND COUNTER > ?}} tail.rising.column = COUNTER
15
At least the rising column is a simple number (and it’s indexed)
Data Sources
! SomeGmes tables can be difficult to retrieve – For example, there is no single primary key/column on which we can
do tailing ê We cannot use DB Connect in such cases
– How to deal with this: ê YEARCOLUMN = 2014 ê TRANSNUM = 1505522 (staring from 0)
– Both are primary keys – DB Connect cannot retrieve this properly
ê Cannot put TRANSNUM as rising column since we have to deal with the case when the year changes
16
Data Sources
! Had to write our own connector – WriZen in Java to ensure portability – Can be executed on any system
ê SomeGmes security requires us to execute the connector locally and ship data through Splunk Universal Forwarder
– Available at ê hZps://github.com/bojanisc/splunk-‐db-‐connect – Can customize as needed – Encrypts configuraGon with a private RSA key – Decrypts on the fly
17
Data Sources
! We can have as many columns saved as state as we want ! The SQL query
– … (( YEARCOLUMN=$YEARCOLUMN$ AND – COUNTER>$COUNTER$) OR – YEARCOLUMN>$YEARCOLUMN$)
! Save YEARCOLUMN and COUNTER as state
18
Processing the Data
! Now that we have everything in Splunk – Time to do some Fraud Monitoring
! Rules generally monitor customer behavior ! Having a rule check a single thing is prone to false posiGves
– Add certain weight to each rule – Sum weights to get the final score
ê Per customer account number, data source or anything we want – Define threshold – If threshold is reached, we have a real alert
ê Helps decrease false posiGve numbers
19
Fraud Monitoring Rules
! Examples we’ll cover – Based on single “notable” events
ê A customer made a transacGon bigger than $X USD to an account that was idle for $Y days, and the account has been created more than $Z days ago
– Based on staGsGcal properGes ê A customer made a transacGon that is 500% bigger than his average transacGon in last three months
! But first, let us see how rules are built generally
20
About Rules
! Rules are saved searches – Executed in real Gme, once per day or as required – They are actually summary searches – Using macros for parameters that can be changed by users
(more about the GUI later)
! index=transacGons sourcetype=transacGons AMOUNT > `rule_1_N` `rule_1_alert_filter` | localop ...
21
[rule_1_N] definiGon = 10000
About Rules
! Rules are saved searches – Executed in real Gme, once per day or as required – They are actually summary searches – Using macros for parameters that can be changed by users
(more about the GUI later)
! index=transacGons sourcetype=transacGons AMOUNT > `rule_1_N` `rule_1_alert_filter` | localop ...
22
It is easy to use macros for filters too
[rule_1_N] definiGon = 10000
About Rules
! Rules are saved searches – Executed in real Gme, once per day or as required – They are actually summary searches – Using macros for parameters that can be changed by users
(more about the GUI later)
! index=transacGons sourcetype=transacGons AMOUNT > `rule_1_N` `rule_1_alert_filter` | localop ...
23
It is easy to use macros for filters too
Tells Splunk to run the rest of the search only locally on the search head
[rule_1_N] definiGon = 10000
About Rules
! index=transacGons sourcetype=transacGons AMOUNT > `rule_1_N` `rule_1_alert_filter` | localop |… | eval RULE_ID="1" …
! | eval GUID=md5(_raw.RULE_ID) ! | lookup local=true rule_searches RULE_ID OUTPUT ALERT_SEVERITY RULE_SCORE
! | `fillnull_rule_1`
24
Create a unique GUID for each alert
About Rules
! index=transacGons sourcetype=transacGons AMOUNT > `rule_1_N` `rule_1_alert_filter` | localop |… | eval RULE_ID="1" …
! | eval GUID=md5(_raw.RULE_ID) ! | lookup local=true rule_searches RULE_ID OUTPUT ALERT_SEVERITY RULE_SCORE
! | `fillnull_rule_1`
25
Create a unique GUID for each alert
A rule’s severity and score are kept in a lookup table
About Rules
! index=transacGons sourcetype=transacGons AMOUNT > `rule_1_N` `rule_1_alert_filter` | localop |… | eval RULE_ID="1" …
! | eval GUID=md5(_raw.RULE_ID) ! | lookup local=true rule_searches RULE_ID OUTPUT ALERT_SEVERITY RULE_SCORE
! | `fillnull_rule_1`
26
Create a unique GUID for each alert
A rule’s severity and score are kept in a lookup table
Make sure all variables we need have value: [fillnull_rule_1] definiGon = fillnull value="-‐" varA varB varC
About Rules
! … | table _Gme GUID RULE_ID ALERT_SEVERITY RULE_SCORE varA varB …
! Store in a summary index ! We will process that with yet another saved search
27
Simple Rules
! Simple searches that just check some thresholds ‒ Example: A customer made a transacGon bigger than $X USD to an account that was idle
for $Y days, and the account has been created more than $Z days ago
index=transacGons sourcetype=transacGons AMOUNT > `rule_1_X` | lookup local=true rule_1 ACCOUNT OUTPUT ISACTIVE CREATIONDATE | where isnull(ISACTIVE) | where abs((tonumber(_Gme) -‐ strpGme(CREATIONDATE, "%Y%m%d"))/86400) > `rule_1_Z`
28
Last acGon and creaGon date are calculated every night and stored in a lookup table
Simple Rules
! Simple searches that just check some thresholds ‒ Example: A customer made a transacGon bigger than $X USD to an account that was idle
for $Y days, and the account has been created more than $Z days ago
index=transacGons sourcetype=transacGons AMOUNT > `rule_1_X` | lookup local=true rule_1 ACCOUNT OUTPUT ISACTIVE CREATIONDATE | where isnull(ISACTIVE) | where abs((tonumber(_Gme) -‐ strpGme(CREATIONDATE, "%Y%m%d"))/86400) > `rule_1_Z`
29
The filter out only transacGons from accounts created more than $Z days ago
Last acGon and creaGon date are calculated every night and stored in a lookup table
Simple Rules ! Simple searches that just check some thresholds ‒ Example: A customer made a transacGon bigger than $X USD to an account that was idle for $Y days,
and the account has been created more than $Z days ago
index=transacGons sourcetype=transacGons AMOUNT > `rule_1_X` | lookup local=true rule_1 ACCOUNT OUTPUT ISACTIVE CREATIONDATE | where isnull(ISACTIVE) | where abs((tonumber(_Gme) -‐ strpGme(CREATIONDATE, "%Y%m%d"))/86400) > `rule_1_Y` | `rule_1_realGme_today` | eval RULE_ID="1" | eval GUID=md5(_raw.RULE_ID) | table _Gme GUID RULE_ID ALERT_SEVERITY varA varB
30
StaGsGcal Rules ! A customer made a transacGon that is 500% bigger than his average transacGon in last three months – We have to prepare data about average transacGons in advance – Even report acceleraGon is too slow (we are dealing with 50 transacGons per
second) – Best way to do this: use lookup tables
index=transacGons sourcetype=transacGons | lookup local=true rule_21_avg_trans ACCOUNT OUTPUT SUM_TRANSACTIONS NUM_TRANSACTIONS | eval AVERAGE_TRANSACTION=SUM_TRANSACTIONS/NUM_TRANSACTIONS | eval THRESHOLD=AVERAGE_TRANSACTION*(100 + `rule_21_X`)/100 | where AMOUNT > THRESHOLD
31
This allows us to calculate the threshold for every account/transacGon
StaGsGcal Rules ! Create lookup tables once per day
– Do not fear to store extra data that might be needed later – Use summary indexes to cascade calculaGons
! Collect data into a summary index, on a per day basis index=transacGons sourcetype=transacGons `rule_21_filter` | stats sum(AMOUNT) AS SUM_AMOUNT count AS NUM_TRANSACTION first(CREATE_DATE) AS ACCOUNT_CREATION_DATE first(CLIENT_ID) AS CLIENT_ID … | eval report="sum_got_fiz_pos" | collect index=summary_clients
32
Add as many as you need (it’s a one-‐pass search)
StaGsGcal Rules ! Create lookup tables once per day
– Do not fear to store extra data that might be needed later – Use summary indexes to cascade calculaGons
! Collect data into a summary index, on a per day basis index=transacGons sourcetype=transacGons `rule_21_filter` | stats sum(AMOUNT) AS SUM_AMOUNT count AS NUM_TRANSACTION first(CREATE_DATE) AS ACCOUNT_CREATION_DATE first(CLIENT_ID) AS CLIENT_ID … | eval report="sum_got_fiz_pos" | collect index=summary_clients
33
Add as many as you need (it’s a one-‐pass search)
And store the results in the summary_clients index
StaGsGcal Rules ! Now use data from the summary index to create the lookup table index=summary_clients report="sum_got_fiz_pos" earliest=-‐`rule_21_Y`d@d latest=@d | stats sum(AMOUNT) AS TOTAL_AMOUNT sum(NUM_TRANSACTION) AS TOTAL_TRANSACTIONS by ACCOUNT | eval AVERAGE_AMOUNT=TOTAL_AMOUNT/TOTAL_TRANSACTIONS | outputlookup rule_21_average_transacGon ! Why doing this in two steps?
– Summary index holds data on a per day basis – If the user wants to recalculate averages, we can do it in maZer of seconds!
34
Making Sure Searches Run in Order ! Call it through our saved search ! | stats count | localop | runsdkmodule "rule_1_learn_lookup.py“ ! This simply calls the rule_1_learn_lookup.py script:
import Gme import splunklib.client as client from se�ngs import * #Sleep Gme between searches sleep_Gme = 15 #Create service instance and log in service = client.connect(host=HOST,port=PORT,username=USERNAME,password=PASSWORD,app=APP) #IniGalize parameters for search blocking_search_parameters = {'exec_mode': 'blocking'} #Define searches searches = [
35
Making Sure Searches Run in Order searches.append('search … | outputlookup rule_16_number_of_transacGons') searches.append('search … | outputlookup rule_16_number_of_transacGon') #Get search jobs object jobs = service.jobs for i in range(len(searches)): #Execute main search job = jobs.create(searches[i], **blocking_search_parameters) #Sleep between searches Gme.sleep(sleep_Gme)
36
PresenGng the Data
! The system must be easy to use for analysts – Most of the Gme they are not technical people – But they understand what the fraud is about
! Top level summary console gives an easy overview of triggered rules – Easy to spot any peaks
37
Top Level Summary Console
38
… and Drill Down
39
Making Life Easier for Users
! Users need a simple GUI to modify parameters, enable and disable rules or even create new rules
40
Easy enable/disable rule (one click)
Making Life Easier for Users
! Users need a simple GUI to modify parameters, enable and disable rules or even create new rules
41
Easy enable/disable rule (one click)
Edit rule parameters (thresholds) through GUI
Making Life Easier for Users
! Users need a simple GUI to modify parameters, enable and disable rules or even create new rules
42
Easy enable/disable rule (one click)
Edit rule parameters (thresholds) through GUI
Validate current se�ngs and their format
Enabling/Disabling Rules
! The on/off buZon makes it easy to enable or disable rules – In the background this simply enables or disables a scheduled search – Full source available at hZps://github.com/bojanisc/splunk-‐onoff-‐search
! Same goes for the rule editor ! These are implemented as JavaScript
– Added into .../appserver/staGc/applicaGon.js ê Executed on every page
43
JavaScript Does the Magic
! When a user clicks on a buZon, we need to display a warning – A�er all, they are about to disable a rule
! And issue a request to a controller to do the job .. if (Splunk.uGl.getCurrentView() == "rule_thresholds") { // here goes our code for on-‐off buZons (jQuery Toggles) // as well as our code for handling rule thresholds ..
44
Add onClick() AcGons // Add acGon for each on-‐off buZon $("> tbody > tr > td > div.toggle-‐light", simpleResultsTable).each(funcGon() {
…
if (!confirm('Are you sure you want to ' + (current_rule_state ? 'disable' : 'enable') + ' this rule?')) {
return false;
}
…
// And call the corresponding Splunk controller
$.ajax({
url: "/" + Splunk.uGl.getConfigValue("LOCALE") + "/custom/fraud_monitoring/RuleThresholdsController/rulestate",
type: "POST",
dataType: "json",
contentType: "applicaGon/json",
data: JSON.stringify({rule_id: rule_id, state: !current_rule_state}),
async: false
…
45
This is our Splunk controller
Splunk Controller
! The controller goes into …/appserver/controllers/RuleThresholdsController.py
// Based on BaseController class RuleThresholdsController(controllers.BaseController): @route('/:rulethresholds=rulethresholds') @expose_page(must_login=True, methods=['POST']) … // Add a route for rule enabling/disabling @route('/:rulestate=rulestate') @expose_page(must_login=True, methods=['GET', 'POST']) def rulestate(self, **kwargs):
46
We simply use GET requests to retrieve a rule’s state, and POST to change it
Splunk Controller // Finally, we modify the status of a saved search saved_searches = service.saved_searches for rule_search_name in rule_search_names: try: saved_search = saved_searches[rule_search_name] … if rule_state: disabled = '0' else: disabled = '1' saved_search.update(disabled = disabled)
47
The result can be seen in the corresponding saved search in savedsearches.conf
Other Elements
! Follow the same procedure: – Add page handling code in applicaGon.js
ê Simply extend the JavaScript code to handle users’ acGons – Add the acGon handling code in a corresponding controller
ê These are Python scripts ! For example, rule threshold changing code does the following
– Display a pop-‐up in JavaScript, get new thresholds – Call a corresponding controller – The controller modifies the macro used for the threshold – The change is applied instantly!
ê Users appreciate this J
48
Fraud Monitoring
! The real “juice” is, of course, in the rules ! But the presentaGon layer must be appropriate for users that will be using this system
! And Splunk provides the engine to handle such huge quanGGes of data
! The end result: a success story that bring ROI back extremely fast
49
Fraud Monitoring and Future
! Add as many data/informaGon sources as possible – Increase quality of rules/searches by minimizing false posiGves
! Integrate Splunk with other systems – Real Gme blocking of transacGons – Easy through web services .. Or any API really
! AddiGonal use cases – Now that we have all the data in Splunk
ê Business intelligence / customer profiling ê MarkeGng
! Be proacGve against the fraudsters
50
INFIGO ReporGng for Splunk
! A Splunk app that allows creaGon of PDF and Microso� Word DOCX reports – Supports any dashboard (both simple and advanced XML) – Add headers, footers, company logo and similar – Create profiles for arbitrary dashboards
! Released as a commercial Splunk applicaGon ! More informaGon: hZp://www.infigo.hr/reporGng ! Or check Splunk Apps
51
INFIGO ReporGng for Splunk
! Example configuraGon screen
52
Takeaways
! Splunk’s pracGcally unlimited flexibility and scalability makes it a perfect plalorm for detecGng all kinds of fraud
! Pull all possible data (both structured and unstructured) into Splunk to increase quality of rules/detecGon
! For best results combine staGc (threshold based) and dynamic rules ! Customize the Splunk applicaGon’s web interface to be as easy to use as possible
53
About INFIGO IS
! Based in CroaGa, operaGng in EMEA ! Splunk’s Most Valuable Partner 3 years in row! ! Providing Splunk professional services
– Strong team, 9 Splunk CerGfied Architects – Security consulGng, penetraGon tesGng
! For more informaGon – hZp://www.infigo.hr – Or e-‐mail [email protected] / [email protected]
54
55
Security office hours: 11:00 AM – 2:00 PM @Room 103 Everyday Geek out, share ideas with Enterprise Security developers
Red Team / Blue Team -‐ Challenge your skills and learn new tricks Mon-‐Wed: 3:00 PM – 6:00 PM @Splunk Community Lounge Thurs: 11:00 AM – 2:00 PM
Learn, share and hack
Birds of a feather-‐ Collaborate and brainstorm with security ninjas Thurs: 12:00 PM – 1:00 PM @Meal Room
THANK YOU