Skip to content

Instantly share code, notes, and snippets.

@Bsebring
Last active February 9, 2024 14:26
Show Gist options
  • Save Bsebring/ec10519ecc272c69be08e82d4b3626af to your computer and use it in GitHub Desktop.
Save Bsebring/ec10519ecc272c69be08e82d4b3626af to your computer and use it in GitHub Desktop.
A Lua script developed to work with Suricata. This script reports malicious IPs to abuseipdb.
#!/usr/bin/env lua
-- Tells Suricata to only execute this script if the
-- packet triggered an alert
function init (args)
local needs = {}
needs["type"] = "packet"
needs["filter"] = "alerts"
return needs
end
-- Setting up the log files to use
function setup (args)
-- The first log will be used to determine if the report
-- was a success or a failure.
filename = SCLogPath() .. "/" .. "abuseipdb_alert_reports.log"
file = assert(io.open(filename, "a"))
-- The second log is a custom log set up for debugging your alerts
filename2 = SCLogPath() .. "/" .. "abuseipdb_custom_debug.log"
file2 = assert(io.open(filename2, "a"))
SCLogInfo("Report to AbuseIPDB " .. filename)
count = 0
end
-- Function that reports to AbuseIPDB and logs
-- the reports to the specified file
function log(args)
local https = require("ssl.https");
local ltn12 = require("ltn12")
local url = require("socket.url")
-- Grab data from packet to use in post request
local ipver, srcip, dstip, proto, sp, dp = SCPacketTuple()
-- Grab timestamp
local timestring = SCPacketTimeString()
local class, prio = SCRuleClass()
local surCategory = "15"
-- Assigning AbuseIPDB category based on Suricata Rule classification
if string.match(class, "web application") then
file2:write("Message: " .. SCRuleMsg() .. " Class: " .. class .. " matched to category 21\n")
surCategory = "21"
elseif string.match(class, "user") or string.match(class, "administrator") then
file2:write("Message: " .. SCRuleMsg() .. " Class: " .. class .. " matched to category 18\n")
surCategory = "18"
elseif string.match(class, "suspicious username") or string.match(class, "default username") then
file2:write("Message: " .. SCRuleMsg() .. " Class: " .. class .. " matched to categories 18, 22\n")
surCategory = "18,22"
elseif (string.match(class, "rpc") or string.match(class, "Network scan") or string.match(class, "Information Leak")) then
file2:write("Message: " .. SCRuleMsg() .. " Class: " .. class .. " matched to category 14\n")
surCategory = "14"
elseif string.match(class, "Denial of Service") then
file2:write("Message: " .. SCRuleMsg() .. " Class: " .. class .. " matched to category 4\n")
surCategory = "4"
else
file2:write("Message: " .. SCRuleMsg() .. " Class: " .. class .. " matched to category 15\n")
surCategory = "15"
end
if string.match(SCRuleMsg(), "SQL INJECTION") then
file2:write("Message: " .. SCRuleMsg() .. " Class: " .. class .. " category 16 added\n")
surCategory = surCategory .. ",16"
end
-- Setting up the post request.
comment = timestring .. " " .. srcip .. " Protocol: " .. proto .. " " .. SCRuleMsg()
commentE = url.escape(comment)
local path = "https://api.abuseipdb.com/api/v2/report?ip=" .. srcip .. "&comment=" .. commentE .. "&categories="
local body = {
categories = "18,22",
["ip"] = srcip
}
local response_body = {}
-- Set up the response request.
local res, code, response_headers = https.request({
url = path .. surCategory,
method = "POST",
headers =
{
["Accept"] = "Application/json",
["Key"] = "$YOUR_API_KEY",
["Content-Length"] = #body,
},
protocol = "tlsv1",
source = ltn12.source.string(tostring(body)),
sink = ltn12.sink.table(response_body)
})
if code == 200 then
file:write ("\nReport " .. count .. " Success! " .. comment .. " " .. table.concat(response_body))
else
file:write ("\nReport " .. count .. " FAILED!" .. timestring .. " Error Code: " .. code .. " Category: " .. surCategory)
end
file:flush()
file2:flush()
count = count + 1
end
-- Cleans up, and closes the log files
function deinit (args)
SCLogInfo ("Reports Logged: " .. count);
io.close(file)
io.close(file2)
end
@camaro23
Copy link

camaro23 commented Jan 2, 2020

Hey @Bsebring,

Thanks for putting together this script! However, I am having some issues and I was wondering if you could help me out.

Unfortunately, I am not seeing any output to the "abuseipdb_alert_reports.log" and the "abuseipdb_custom_debug.log" when a Suricata alert is triggered. I followed all of the instructions in the blog and installed all of the necessary Luarocks. When I enable the lua script in the Suricata.yaml file and restart suricata, the two log files are created, but nothing is ever written to the logs. I know Suricata is reading the script on runtime, because the log files are created, but that is all I see happening. Unfortunately, I am getting no debug information neither, so I am hoping there is something else I can use to troubleshoot, or if you know of any other necessary steps.

Thanks for the help!

@Bsebring
Copy link
Author

Bsebring commented Jan 2, 2020 via email

@camaro23
Copy link

camaro23 commented Jan 2, 2020

Hey Bsebring,

Thanks for getting back to me so soon!

Thanks for getting back to me.

I had already restarted Suricata after adding the Lua script to the Suricata.yaml file, but I did not restart Suricata with the restart script that you guys provided. Looking at the restart script, I do not think that how I restarted Suricata would make a difference, however, I do see that you guys start Suricata back up in af_packet mode, which I am not using. Could that be the cause for my issue? Although I do not expect it would. Do you know of a way to see if the Suricata engine is even attempting to run the Lua script when it detects an alert? Unfortunately, I am pretty blind in terms of troubleshooting and can only assume that Suricata is trying to run the script because when I remove the two log files and restart Suricata, Suricata does create the new logs but nothing is ever written to them.

Maybe we could try manually kicking off the script to see if there are any issues with the api request? I have manually ran api request from the box running Suricata as described here: https://docs.abuseipdb.com/#introduction, and have no problem receiving a response from the api server so I assume my api credentials and network connection are not an issue. But, I would like to manually fire off the script if possible to ensure that no errors are occurring in the Lua script. Any insight would be greatly appreciated!

Thanks for the help!

@arafatx
Copy link

arafatx commented Oct 22, 2020

Hey Bsebring,

Thanks for getting back to me so soon!

Thanks for getting back to me.

I had already restarted Suricata after adding the Lua script to the Suricata.yaml file, but I did not restart Suricata with the restart script that you guys provided. Looking at the restart script, I do not think that how I restarted Suricata would make a difference, however, I do see that you guys start Suricata back up in af_packet mode, which I am not using. Could that be the cause for my issue? Although I do not expect it would. Do you know of a way to see if the Suricata engine is even attempting to run the Lua script when it detects an alert? Unfortunately, I am pretty blind in terms of troubleshooting and can only assume that Suricata is trying to run the script because when I remove the two log files and restart Suricata, Suricata does create the new logs but nothing is ever written to them.

Maybe we could try manually kicking off the script to see if there are any issues with the api request? I have manually ran api request from the box running Suricata as described here: https://docs.abuseipdb.com/#introduction, and have no problem receiving a response from the api server so I assume my api credentials and network connection are not an issue. But, I would like to manually fire off the script if possible to ensure that no errors are occurring in the Lua script. Any insight would be greatly appreciated!

Thanks for the help!

Actually it's working if you put file.flush() after writing to the file. Without flush, I also got 2 empty files. Weird the example from abuseipdb doesn't mention about this and it looks like a magic thing that it's working on their side.

Add the code just before the count variable:

...
file:flush()
file2:flush()
alert_count = alert_count + 1

@Bsebring
Copy link
Author

Hey Bsebring,
Thanks for getting back to me so soon!
Thanks for getting back to me.
I had already restarted Suricata after adding the Lua script to the Suricata.yaml file, but I did not restart Suricata with the restart script that you guys provided. Looking at the restart script, I do not think that how I restarted Suricata would make a difference, however, I do see that you guys start Suricata back up in af_packet mode, which I am not using. Could that be the cause for my issue? Although I do not expect it would. Do you know of a way to see if the Suricata engine is even attempting to run the Lua script when it detects an alert? Unfortunately, I am pretty blind in terms of troubleshooting and can only assume that Suricata is trying to run the script because when I remove the two log files and restart Suricata, Suricata does create the new logs but nothing is ever written to them.
Maybe we could try manually kicking off the script to see if there are any issues with the api request? I have manually ran api request from the box running Suricata as described here: https://docs.abuseipdb.com/#introduction, and have no problem receiving a response from the api server so I assume my api credentials and network connection are not an issue. But, I would like to manually fire off the script if possible to ensure that no errors are occurring in the Lua script. Any insight would be greatly appreciated!
Thanks for the help!

Actually it's working if you put file.flush() after writing to the file. Without flush, I also got 2 empty files. Weird the example from abuseipdb doesn't mention about this and it looks like a magic thing that it's working on their side.

Add the code just before the count variable:

...
file:flush()
file2.flush()
alert_count = alert_count + 1

Thank you very much for adding this bit of information, I will verify that this works on our end and update the script accordingly.

@arafatx
Copy link

arafatx commented Oct 25, 2020

I have question, the default rate limit for API is 3000, how do I obtain the json value for this, so that I can display it in my script. I see the documentation only explained about the header error code. Is there a specific json value? Also is there an API key to test the maximum rate ?

@Bsebring
Copy link
Author

Bsebring commented Jan 9, 2021

I have question, the default rate limit for API is 3000, how do I obtain the json value for this, so that I can display it in my script. I see the documentation only explained about the header error code. Is there a specific json value? Also is there an API key to test the maximum rate ?

We do have the X-RateLimit-Limit header that returns your daily limit, as well as one that returns the number of daily requests remaining.

Not really sure what you mean by the last question, we do all of our testing with the maximum limit.

We also offer a trial on our premium subscription.

@ehnwebmaster
Copy link

ehnwebmaster commented Mar 7, 2021

Hello,

Nice script, but needs a counter (per IP) or timer 15 minutes) to prevent send the same ip report multiple times. You must wait at least 15 minutes for report for the same IP.

Here's my case: if some stupid IP makes a multiple attack:

Message: ATTACK [PTsecurity] Unimplemented Trans2 Sub-Command code. Possible ETERNALBLUE (WannaCry, Petya) tool Class: Attempted Administrator Privilege Gain matched to category 15

For every line (even repeated) the script sent the attack every time to AbuseIPDB, and finally gets a Error Code 429

Report 2217 FAILED!03/07/2021-20:53:29.728015 Error Code: 429 Category: 15

May I add?

  elseif string.match(class, "Attempted Administrator Privilege Gain") then
    return

Context:
I'm runing a Honeypot on port 445 (SMB)

Same happens with other duplicate rules:

Message: GPL NETBIOS SMB-DS IPC$ share access Class: Generic Protocol Command Decode matched to category 15

Any ideas?

Respond myself:

Here is the solution using "threshold" /etc/suricata/threshold.config

threshold gen_id 1, sig_id 2102465, type both, track by_src, count 1, seconds 3600

Where sig_id is the rule.

Documentation:
https://suricata.readthedocs.io/en/suricata-6.0.2/configuration/global-thresholds.html#threshold-event-filter
https://suricata.readthedocs.io/en/suricata-6.0.2/rules/thresholding.html

Thank you in advance.

@alex-sandro92
Copy link

I had to change protocol version to "tlsv1_2" at line 85 to make it work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment