- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-12-2019 01:46 PM
Hello all,
I have created (or modified) an inbound action that creates an SIR, then adds subject to short description and so on. Everything works as expected and my regular expressions match what I am attempting to parse, however how do I get my inbound action to map these to a related list, m2m_task_observable? (I don't need help with getting my parsing to work instead, I need help with getting my syntax below to take the parsed data and lookup or insert it correctly in the right related list).
I am having to do this because the email parser in security operations is very limited and seems to only want to parse the first instance of an ip, url or hash and doesn't seem to permit me to use a null value as a separator. Therefore I chose to parse any occurrence of an ip, url or hash value and I'd like to lookup or insert a record into m2m_task_observables just as I would with the email parsing feature. Would someone take a moment to see what I am missing here?
(function runAction(/GlideRecord/ current,/GlideRecord/ event,/EmailWrapper/ email,/ScopedEmailLogger/ logger, /EmailClassifiers/
classifier) {
// Implement email action here
current.short_description = email.subject + " - " + "Possible Malware Email Reported";
current.description = email.body_text;
//get affected by from reported_by value
var userID = email.body.reported_by;
gs.info('INBOUND TEST: ' + userID);
if (userID != '') {
var tUser = '';
var gr = new GlideRecord('sys_user');
gr.addQuery('email', userID);
gr.addActiveQuery();
gr.query();
gs.info('INBOUND 2: ' + gr.getRowCount());
if (gr.getRowCount() == 1) {
while (gr.next()) {
gs.info('DK INBOUND 3: ' + gr.getRowCount());
current.affected_user = gr.sys_id;
current.u_region = gr.u_region;
current.u_country = gr.u_proper_country;
current.location = gr.location;
current.category = "Phishing";
if (gr.vip == true) {
current.subcategory = ' VIP Target';
} else {
current.subcategory = ' Non-VIP Target';
}
}
}
var lines = email.body_text.val().split('<br />');
for (var i = 0; i < lines.length; i++) {
//code here using lines[i] which will give you each line of the message
var urlRegex = /^hxx(p|ps)/i;
var ipRegex = /^\d+\.\d+\.\d+\.\d+$/;
var urlResult = urlRegex.test(lines[i]);
var ipResult = ipRegex.test(lines[i]);
if (urlResult) {
//should go to something like sn_ti_m2m_task_observable, still needs something to strip the defang and extract the FQDN
var defangedURL = lines[i];
var fangedURL = defangedURL.replace(/\[\.\]/g,".");
var fangedDomain = fangedURL.match(/(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.){1,}([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]){2,}/)[0];
current.sn_ti_m2m_task_observable += fangedDomain;
}else if (ipResult) {
current.sn_ti_m2m_task_observable += lines[i];
}
}
}
// logger.logError(JSON.stringify(email));
})(current, event, email, logger, classifier);
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-12-2019 04:43 PM
Hey R34rvi3w!
Andy has a great suggestion above - it is the easiest way to go.
You can use the following field names to populate those accelerator fields:
current.source_ip
current.dest_ip
current.other_ioc
current.malware_hash
That said your original method would also have worked, however there is a logical/syntax error and it may help you to understand the root of the original failure, so I'll try to give more info here.
The short version is that you cannot modify an m2m table by dot walking through current - this is where things break:
current.sn_ti_m2m_task_observable += fangedDomain;
In order to manipulate this table, it will require a valid observable record (which you may need to make), and a separate GlideRecord query.
For example - here is some pseudo-code - it will not be tested but should be close to what you need, assuming you go down this harder road:
// Replace yourValue below in two places..
// We need an observable first
var obs = new GlideRecord('sn_ti_observable');
obs.addQuery('value', yourValue);
obs.query();
if (! obs.hasNext()) {
// Observable not found, make one
obs.initialize();
obs.value = yourValue;
obs.insert();
} else {
obs.next(); // Assumes there is only one with that value...
}
// Create a new m2m pairing the observable and incident
var m2m = new GlideRecord('sn_ti_m2m_task_observable');
m2m.initialize();
m2m.task = current.sys_id;
m2m.observable = obs.sys_id;
m2m.insert();
Hope this helps!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-12-2019 03:27 PM
Hey there,
Here's one approach you might want to take a look at.
- Have you seen the Business Rule called "Handle Deprecated Observable Fields" on the SIR Table?
-
- There are certain fields that exist on the SIR table (Source IP, Destination IP, Malware URL, etc) -> that have special logic handled by this Biz Rule...
- When you add a value to these fields for a given SIR record, they will automatically create Observables (if they do not already exist), and associate them to the SIR record
- You can look at leveraging this functionality (i.e. putting your parsed values into these fields, checking to see that they correctly establish Observables associated to the SIR (via the M2M table)
- I also believe these can handle comma separated values - you can try to test that out as well
- Alternatively, there's a Script Include called "AddBulkObservablesToSI" you might want to look at:
-
- There are functions that you might be able to use, or reference to create your own functions -> to handle this.
Reference - Using the logic from "Handle Deprecated Observable Fields" Biz Rule:
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-12-2019 04:43 PM
Hey R34rvi3w!
Andy has a great suggestion above - it is the easiest way to go.
You can use the following field names to populate those accelerator fields:
current.source_ip
current.dest_ip
current.other_ioc
current.malware_hash
That said your original method would also have worked, however there is a logical/syntax error and it may help you to understand the root of the original failure, so I'll try to give more info here.
The short version is that you cannot modify an m2m table by dot walking through current - this is where things break:
current.sn_ti_m2m_task_observable += fangedDomain;
In order to manipulate this table, it will require a valid observable record (which you may need to make), and a separate GlideRecord query.
For example - here is some pseudo-code - it will not be tested but should be close to what you need, assuming you go down this harder road:
// Replace yourValue below in two places..
// We need an observable first
var obs = new GlideRecord('sn_ti_observable');
obs.addQuery('value', yourValue);
obs.query();
if (! obs.hasNext()) {
// Observable not found, make one
obs.initialize();
obs.value = yourValue;
obs.insert();
} else {
obs.next(); // Assumes there is only one with that value...
}
// Create a new m2m pairing the observable and incident
var m2m = new GlideRecord('sn_ti_m2m_task_observable');
m2m.initialize();
m2m.task = current.sys_id;
m2m.observable = obs.sys_id;
m2m.insert();
Hope this helps!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-23-2019 05:02 PM
Thanks @Alex Cox and @./andy-b2poYQ==
I tried using two of those accelerator fields and nothing was populated in observables. I checked the regular expression and it seems to be valid for parsing something like the following. While this also may seem silly, I was not able to locate that business rule in my Madrid instance. Here's what I'm having to parse and get into that observable table.
---------------------------------------------------------
Indicators of Compromise (IOCs):
Observed Infection URL(s):
hXXps://mcastedu-my[.]sharepoint[.]com/:o:/g/personal/tunde_william_c12408_mcast_edu_mt/EmHKRM9oD51HgJxrxDzZ-jwBtbG3IaTp0gUDjuCD34M9Pg
Observed Infection IP(s):
13.107.136.9
Observed Payload URL(s):
hXXps://www[.]genxo[.]ml//secured/doc/%40%40%23%23/
Observed Payload IP(s):
104.27.177.24
104.27.176.24
----------------------------------------------------------
While the regular expression seems to match - I still seem to be missing something needed to get this to populate the observables. Is there something I can check to determine if the parsing is at least working but failing to insert into current? Any other ideas from the revised script below?
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifiers*/ classifier) {
// Implement email action here
current.short_description = email.subject + " - " + "User Reported";
current.description = email.body_text;
//get affected by from reported_by value
var userID = email.body.reported_by;
gs.info('INBOUND TEST: ' + userID);
if (userID != '') {
var tUser = '';
var gr = new GlideRecord('sys_user');
gr.addQuery('email', userID);
gr.addActiveQuery();
gr.query();
gs.info('INBOUND 2: ' + gr.getRowCount());
if (gr.getRowCount() == 1) {
while (gr.next()) {
gs.info('INBOUND 3: ' + gr.getRowCount());
current.affected_user = gr.sys_id;
current.u_region = gr.u_region;
current.u_country = gr.u_proper_country;
current.location = gr.location;
current.category = "Phishing";
if (gr.vip == true) {
current.subcategory = ' VIP Target';
} else {
current.subcategory = ' Non-VIP Target';
}
}
}
var lines = email.body_text.val().split(/<br>/);
for (var i = 0; i < lines.length; i++) {
//code here using lines[i] which will give you each line of the message
var urlRegex = /^hxx(p|ps)/i;
var ipRegex = /^\d+\.\d+\.\d+\.\d+$/;
var urlResult = urlRegex.test(lines[i]);
var ipResult = ipRegex.test(lines[i]);
var hashRegex = /^sha256\:\s+/i;
var hashResult = hashRegex.test(lines[i]);
if (urlResult) {
//should go to something like sn_ti_m2m_task_observable, still needs something to strip the defang and extract the FQDN
var defangedURL = lines[i];
var fangedURL = defangedURL.replace(/\[\.\]/g, ".");
var fangedDomain = fangedURL.match(/(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.){1,}([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]){2,}/)[0];
current.other_ioc += fangedDomain;
} else if (ipResult) {
current.dest_ip += lines[i];
} else if (hashResult) {
var lineSHA256 = lines[i];
var strippedSHA256 = lineSHA256.match(/[A-Fa-f0-9]{64}/)[0];
current.malware_hash += strippedSHA256;
}
}
}
// logger.logError(JSON.stringify(email));
})(current, event, email, logger, classifier);
/*
Legacy fields
current.source_ip
current.dest_ip //We're using dest_ip for the email parser
current.other_ioc
current.malware_hash
*/
I'm open to any thoughts here?
Thank you NOW community!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-24-2019 04:40 PM
Hey there,
I think there might be a slight adjustment needed on Line.31 - where you do your `Split`.
Using [email.body_text] should kick out text, vs HTML.
You can try to switch your split from:
var lines = email.body_text.val().split(/<br>/);
To:
//var lines = email.body_text.val().split(/<br>/);
var lines = email.body_text.split('\n'); //<-- this is text, not HTML
Full transparency - I'm not a regex ninja 🙂
I kicked the tires using your script as an example, and leveraged OOTB Functions includes with SecOps to do the Regex validation, and attempted to create an SIR from an email to handle an IPV4 addr.
It worked successfully:
- Created the SIR, added the IPV4 to [dest_ip] field on the SIR
- Associated the IPV4 as an Observable to the SIR (via the Biz Rule for accelerator fields)
Not sure why you don't have that Biz Rule for the accelerator fields.
- Attached an XML export of the Biz Rule here, if you want to import it and do some testing with it.
Here is the updated array iteration I kicked the tires at:
//var lines = email.body_text.val().split(/<br>/);
var lines = email.body_text.split('\n'); //<-- this is text, not HTML
for (var i=0; i<lines.length; i++) {
//code here using lines[i] which will give you each line of the message
gs.info ("test val is " + lines[i]);
if (new sn_sec_cmn.RegexValidationUtil().isIPV4Valid(lines[i])) {
current.dest_ip = lines[i];
gs.info("matched to an IP!" + lines[i]);
}
}
Here's the email for simulating:
Here is the SIR that was created for the IPV4 addr:
Here is the updated script that you can iterate from:
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifiers*/ classifier) {
// Implement email action here
current.short_description = email.subject + " - " + "User Reported";
current.description = email.body_text;
//get affected by from reported_by value
var userID = email.body.reported_by;
gs.info('INBOUND TEST: ' + userID);
if (userID != '') {
var tUser = '';
var gr = new GlideRecord('sys_user');
gr.addQuery('email', userID);
gr.addActiveQuery();
gr.query();
gs.info('INBOUND 2: ' + gr.getRowCount());
if (gr.getRowCount() == 1) {
while (gr.next()) {
gs.info('INBOUND 3: ' + gr.getRowCount());
current.affected_user = gr.sys_id;
current.u_region = gr.u_region;
current.u_country = gr.u_proper_country;
current.location = gr.location;
current.category = "Phishing";
if (gr.vip == true) {
current.subcategory = ' VIP Target';
} else {
current.subcategory = ' Non-VIP Target';
}
}
}
//var lines = email.body_text.val().split(/<br>/);
var lines = email.body_text.split('\n');
for (var i=0; i<lines.length; i++) {
//code here using lines[i] which will give you each line of the message
gs.info ("test val is " + lines[i]);
if (new sn_sec_cmn.RegexValidationUtil().isIPV4Valid(lines[i])) {
current.dest_ip = lines[i];
gs.info("matched to an IP!" + lines[i]);
}
}
}
// logger.logError(JSON.stringify(email));
})(current, event, email, logger, classifier);
/*
Legacy fields
current.source_ip
current.dest_ip //We're using dest_ip for the email parser
current.other_ioc
current.malware_hash
*/