Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
W
Warden Connectors
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
713
Warden
Warden Connectors
Commits
baa347b7
Commit
baa347b7
authored
7 years ago
by
Pavel Eis
Committed by
Pavel Kácha
2 years ago
Browse files
Options
Downloads
Patches
Plain Diff
Added connector for Suricata IDS.
parent
7c2e3e58
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
suricata/SuricataToIdea.py
+426
-0
426 additions, 0 deletions
suricata/SuricataToIdea.py
with
426 additions
and
0 deletions
suricata/SuricataToIdea.py
0 → 100644
+
426
−
0
View file @
baa347b7
import
socket
import
json
from
uuid
import
uuid4
import
re
import
optparse
import
sys
import
os
import
signal
import
resource
import
os.path
as
pth
import
atexit
import
time
from
collections
import
OrderedDict
import
logging
class
FileWatcher
(
object
):
def
__init__
(
self
,
filename
,
tail
=
True
):
self
.
filename
=
filename
self
.
open
()
self
.
line_buffer
=
""
if
tail
and
self
.
f
:
self
.
f
.
seek
(
0
,
os
.
SEEK_END
)
def
open
(
self
):
'''
opens file, stored in self.filename and stores i-node and size of file to object
:return: nothing
'''
try
:
self
.
f
=
open
(
self
.
filename
,
"
r
"
)
st
=
os
.
fstat
(
self
.
f
.
fileno
())
# stores file stats get from filedescriptor
self
.
inode
,
self
.
size
=
st
.
st_ino
,
st
.
st_size
except
IOError
:
self
.
f
=
None
self
.
inode
=
-
1
self
.
size
=
-
1
def
_check_reopen
(
self
):
'''
if i-node or size of opened filed changed(someone renamed it, deleted it), then reopen file
:return: nothing
'''
try
:
st
=
os
.
stat
(
self
.
filename
)
cur_inode
,
cur_size
=
st
.
st_ino
,
st
.
st_size
except
OSError
as
e
:
cur_inode
=
-
1
cur_size
=
-
1
if
cur_inode
!=
self
.
inode
or
cur_size
<
self
.
size
:
self
.
close
()
self
.
open
()
def
readline
(
self
):
'''
opens or reopens file and reads one line from it and appends it to self.line_buffer
:return: self.line_buffer
'''
if
not
self
.
f
:
self
.
open
()
if
not
self
.
f
:
return
self
.
line_buffer
res
=
self
.
f
.
readline
()
if
not
res
:
self
.
_check_reopen
()
if
not
self
.
f
:
return
self
.
line_buffer
res
=
self
.
f
.
readline
()
if
not
res
.
endswith
(
"
\n
"
):
self
.
line_buffer
+=
res
else
:
res
=
self
.
line_buffer
+
res
self
.
line_buffer
=
""
return
res
def
close
(
self
):
try
:
if
self
.
f
:
self
.
f
.
close
()
except
IOError
:
pass
self
.
inode
=
-
1
self
.
size
=
-
1
def
__repr__
(
self
):
return
'
%s(
"
%s
"
)
'
%
(
type
(
self
).
__name__
,
self
.
filename
)
def
__enter__
(
self
):
return
self
def
__exit__
(
self
,
exc_type
,
exc_val
,
exc_tb
):
self
.
close
()
return
False
def
__iter__
(
self
):
return
self
def
next
(
self
):
return
self
.
readline
()
class
Filer
(
object
):
def
__init__
(
self
,
directory
):
self
.
basedir
=
self
.
_ensure_path
(
directory
)
self
.
tmp
=
self
.
_ensure_path
(
pth
.
join
(
self
.
basedir
,
"
tmp
"
))
self
.
incoming
=
self
.
_ensure_path
(
pth
.
join
(
self
.
basedir
,
"
incoming
"
))
self
.
hostname
=
socket
.
gethostname
()
self
.
pid
=
os
.
getpid
()
def
_ensure_path
(
self
,
p
):
try
:
os
.
mkdir
(
p
)
except
OSError
:
if
not
pth
.
isdir
(
p
):
raise
return
p
def
_get_new_name
(
self
,
fd
=
None
):
(
inode
,
device
)
=
os
.
fstat
(
fd
)[
1
:
3
]
if
fd
else
(
0
,
0
)
return
"
%s.%d.%f.%d.%d
"
%
(
self
.
hostname
,
self
.
pid
,
time
.
time
(),
device
,
inode
)
def
create_unique_file
(
self
):
# First find and open name unique within tmp
tmpname
=
None
while
not
tmpname
:
tmpname
=
self
.
_get_new_name
()
try
:
fd
=
os
.
open
(
pth
.
join
(
self
.
tmp
,
tmpname
),
os
.
O_CREAT
|
os
.
O_RDWR
|
os
.
O_EXCL
)
except
OSError
as
e
:
if
e
.
errno
!=
errno
.
EEXIST
:
raise
# other errors than duplicates should get noticed
tmpname
=
None
# Now we know the device/inode, rename to make unique within system
newname
=
self
.
_get_new_name
(
fd
)
os
.
rename
(
pth
.
join
(
self
.
tmp
,
tmpname
),
pth
.
join
(
self
.
tmp
,
newname
))
nf
=
os
.
fdopen
(
fd
,
"
w
"
)
return
nf
,
newname
def
publish_file
(
self
,
short_name
):
os
.
rename
(
pth
.
join
(
self
.
tmp
,
short_name
),
pth
.
join
(
self
.
incoming
,
short_name
))
class
IdeaGen
(
object
):
idea_categories
=
{
'
Intrusion.Botnet
'
:
re
.
compile
(
"
A Network Trojan was detected, signature: ET CNC
"
),
'
Malware.Trojan
'
:
re
.
compile
(
"
A Network Trojan was detected, signature: ET (?!CNC)
"
),
'
Recon.Scanning
'
:
re
.
compile
(
"
signature: (ET|GPL) (SCAN|DNS Query|CURRENT_EVENTS DNS Query)
"
),
'
Availability.DoS
'
:
re
.
compile
(
"
signature: ET DOS
"
),
'
Abusive.Spam
'
:
re
.
compile
(
"
signature:.*?Spam(?!cop.net)
"
),
'
Attempt.Exploit
'
:
re
.
compile
(
"
category: Web Application Attack
"
)}
other_a_re
=
re
.
compile
(
"
signature: (GPL SNMP|GPL WEB_SERVER|ET DROP Dshield|ET WEB_CLIENT Hex Obfuscation|GPL TELNET)
"
)
other_b_re
=
re
.
compile
(
"
Potential Corporate Privacy Violation, signature: ET (?!POLICY)
"
)
def
__init__
(
self
,
name
,
test
):
self
.
name
=
name
self
.
test
=
test
def
convert_category
(
self
,
category
,
signature
):
if
not
(
category
and
signature
):
return
None
suricata_category
=
"
category:
"
+
category
+
"
, signature:
"
+
signature
if
IdeaGen
.
other_a_re
.
search
(
suricata_category
)
or
IdeaGen
.
other_b_re
.
search
(
suricata_category
):
return
"
Other
"
for
category
,
pattern
in
IdeaGen
.
idea_categories
.
items
():
if
pattern
.
search
(
suricata_category
):
return
category
return
None
def
gen_event_idea
(
self
,
timestamp
,
category
,
src_ip
,
src_port
,
proto
,
dest_ip
,
dest_port
,
orig_data
,
incident_desription
):
event
=
{
'
Format
'
:
"
IDEA0
"
,
'
ID
'
:
str
(
uuid4
()),
'
DetectTime
'
:
timestamp
,
'
Category
'
:
[
category
]
+
([
"
Test
"
]
if
self
.
test
else
[]),
'
Note
'
:
incident_desription
}
source
=
{}
target
=
{}
if
src_ip
:
af
=
"
IP4
"
if
not
'
:
'
in
src_ip
else
"
IP6
"
source
[
af
]
=
[
src_ip
]
if
src_port
:
source
[
'
Port
'
]
=
[
src_port
]
if
proto
:
source
[
'
Proto
'
]
=
[
proto
]
if
dest_ip
:
af
=
"
IP4
"
if
not
'
:
'
in
dest_ip
else
"
IP6
"
target
[
af
]
=
[
dest_ip
]
if
dest_port
:
target
[
'
Port
'
]
=
[
dest_port
]
if
source
:
event
[
'
Source
'
]
=
[
source
]
if
target
:
event
[
'
Target
'
]
=
[
target
]
if
orig_data
:
attachment
=
{
'
Handle
'
:
"
att1
"
,
'
Note
'
:
"
original data
"
,
'
Content
'
:
orig_data
}
event
[
'
Attach
'
]
=
attachment
event
[
'
Node
'
]
=
[{
'
Name
'
:
self
.
name
,
'
Type
'
:
[
"
Connection
"
,
"
Honeypot
"
,
"
Recon
"
],
'
SW
'
:
[
"
HP Tipping Point
"
],
}]
return
event
def
daemonize
(
work_dir
=
None
,
chroot_dir
=
None
,
umask
=
None
,
uid
=
None
,
gid
=
None
,
pidfile
=
None
,
files_preserve
=
[],
signals
=
{}):
# Dirs, limits, users
if
chroot_dir
is
not
None
:
os
.
chdir
(
chroot_dir
)
os
.
chroot
(
chroot_dir
)
if
umask
is
not
None
:
os
.
umask
(
umask
)
if
work_dir
is
not
None
:
os
.
chdir
(
work_dir
)
if
gid
is
not
None
:
os
.
setgid
(
gid
)
if
uid
is
not
None
:
os
.
setuid
(
uid
)
# Doublefork, split session
if
os
.
fork
()
>
0
:
os
.
_exit
(
0
)
try
:
os
.
setsid
()
except
OSError
:
pass
if
os
.
fork
()
>
0
:
os
.
_exit
(
0
)
# Setup signal handlers
for
(
signum
,
handler
)
in
signals
.
items
():
signal
.
signal
(
signum
,
handler
)
# Close descriptors
descr_preserve
=
set
(
f
.
fileno
()
for
f
in
files_preserve
)
maxfd
=
resource
.
getrlimit
(
resource
.
RLIMIT_NOFILE
)[
1
]
if
maxfd
==
resource
.
RLIM_INFINITY
:
maxfd
=
65535
for
fd
in
range
(
maxfd
,
3
,
-
1
):
# 3 means omit stdin, stdout, stderr
if
fd
not
in
descr_preserve
:
try
:
os
.
close
(
fd
)
except
Exception
:
pass
# Redirect stdin, stdout, stderr to /dev/null
devnull
=
os
.
open
(
os
.
devnull
,
os
.
O_RDWR
)
for
fd
in
range
(
3
):
os
.
dup2
(
devnull
,
fd
)
# PID file
if
pidfile
is
not
None
:
pidd
=
os
.
open
(
pidfile
,
os
.
O_RDWR
|
os
.
O_CREAT
|
os
.
O_EXCL
|
os
.
O_TRUNC
)
os
.
write
(
pidd
,
str
(
os
.
getpid
())
+
"
\n
"
)
os
.
close
(
pidd
)
# Define and setup atexit closure
@atexit.register
def
unlink_pid
():
try
:
os
.
unlink
(
pidfile
)
except
Exception
:
pass
def
get_args
():
optp
=
optparse
.
OptionParser
(
usage
=
"
usage: %prog [options] logfile ...
"
,
description
=
"
Watch Suricata logfiles and generate Idea events into directory
"
)
optp
.
add_option
(
"
-n
"
,
"
--name
"
,
default
=
None
,
dest
=
"
name
"
,
type
=
"
string
"
,
action
=
"
store
"
,
help
=
"
Warden client name
"
)
optp
.
add_option
(
"
--test
"
,
default
=
False
,
dest
=
"
test
"
,
action
=
"
store_true
"
,
help
=
"
Add Test category
"
)
optp
.
add_option
(
"
-o
"
,
"
--oneshot
"
,
default
=
False
,
dest
=
"
oneshot
"
,
action
=
"
store_true
"
,
help
=
"
process files and quit (do not daemonize)
"
)
optp
.
add_option
(
"
--poll
"
,
default
=
1
,
dest
=
"
poll
"
,
type
=
"
int
"
,
action
=
"
store
"
,
help
=
"
log file polling interval
"
)
optp
.
add_option
(
"
-d
"
,
"
--dir
"
,
default
=
None
,
dest
=
"
dir
"
,
type
=
"
string
"
,
action
=
"
store
"
,
help
=
"
Target directory (mandatory)
"
)
optp
.
add_option
(
"
-p
"
,
"
--pid
"
,
default
=
pth
.
join
(
"
/var/run
"
,
pth
.
splitext
(
pth
.
basename
(
sys
.
argv
[
0
]))[
0
]
+
"
.pid
"
),
dest
=
"
pid
"
,
type
=
"
string
"
,
action
=
"
store
"
,
help
=
"
create PID file with this name (default: %default)
"
)
optp
.
add_option
(
"
-u
"
,
"
--uid
"
,
default
=
None
,
dest
=
"
uid
"
,
type
=
"
int
"
,
action
=
"
store
"
,
help
=
"
user id to run under
"
)
optp
.
add_option
(
"
-g
"
,
"
--gid
"
,
default
=
None
,
dest
=
"
gid
"
,
type
=
"
int
"
,
action
=
"
store
"
,
help
=
"
group id to run under
"
)
optp
.
add_option
(
"
--origdata
"
,
default
=
False
,
dest
=
"
origdata
"
,
action
=
"
store_true
"
,
help
=
"
Store original report to IDEA message
"
)
return
optp
def
save_events
(
event
,
filer
):
f
,
name
=
filer
.
create_unique_file
()
with
f
:
f
.
write
(
json
.
dumps
(
event
,
ensure_ascii
=
True
))
filer
.
publish_file
(
name
)
running_flag
=
True
reload_flag
=
False
def
terminate_me
(
signum
,
frame
):
global
running_flag
running_flag
=
False
def
reload_me
(
signum
,
frame
):
global
reload_flag
reload_flag
=
True
def
main
():
global
reload_flag
global
running_flag
logging
.
basicConfig
(
format
=
'
%(levelname)s:%(message)s
'
,
level
=
logging
.
DEBUG
,
filename
=
'
suricataDaemonLog.log
'
,
filemode
=
'
w
'
)
optp
=
get_args
()
# win
'''
opts, args = optp.parse_args([
"
--origdata
"
,
"
-d
"
,
"
C:
\\
Users
\\
Aisik
\\
PycharmProjects
\\
SuricataToIdea
\\
IdeaLogTest.txt
"
,
"
-n
"
,
"
cz.cesnet.server.suricata
"
,
"
C:
\\
Users
\\
Aisik
\\
PycharmProjects
\\
SuricataToIdea
\\
"
"
var
\\
log
\\
suricata
\\
suricataJsonLog.txt
"
])
'''
# linux
opts
,
args
=
optp
.
parse_args
([
"
--origdata
"
,
"
-d
"
,
"
/root/Dokumenty/PycharmProjects/SuricataToIdea/IdeaLogTest
"
,
"
-n
"
,
"
cz.cesnet.server.suricata
"
,
"
/root/Dokumenty/PycharmProjects/SuricataToIdea/
"
"
var/log/suricata/suricataJsonLog.txt
"
])
if
not
args
or
opts
.
name
is
None
or
opts
.
dir
is
None
:
optp
.
print_help
()
sys
.
exit
()
if
opts
.
oneshot
:
signal
.
signal
(
signal
.
SIGINT
,
terminate_me
)
signal
.
signal
(
signal
.
SIGTERM
,
terminate_me
)
files
=
[
open
(
arg
)
for
arg
in
args
]
else
:
daemonize
(
pidfile
=
opts
.
pid
,
uid
=
opts
.
uid
,
gid
=
opts
.
gid
,
signals
=
{
signal
.
SIGINT
:
terminate_me
,
signal
.
SIGTERM
:
terminate_me
,
signal
.
SIGHUP
:
reload_me
})
files
=
[
FileWatcher
(
arg
)
for
arg
in
args
]
filer
=
Filer
(
opts
.
dir
)
idea_gen
=
IdeaGen
(
opts
.
name
,
opts
.
test
)
while
running_flag
:
for
log_file
in
files
:
while
True
:
try
:
line
=
log_file
.
readline
()
if
line
is
None
or
not
line
.
strip
():
logging
.
info
(
"
no line
"
)
break
log
=
json
.
loads
(
line
)
logging
.
info
(
"
readline
"
)
category
=
idea_gen
.
convert_category
(
category
=
log
.
get
(
'
alert
'
).
get
(
'
category
'
),
signature
=
log
.
get
(
'
alert
'
).
get
(
'
signature
'
))
if
category
and
log
.
get
(
'
timestamp
'
):
idea_event
=
idea_gen
.
gen_event_idea
(
timestamp
=
log
[
'
timestamp
'
],
category
=
category
,
src_ip
=
log
.
get
(
'
src_ip
'
),
src_port
=
log
.
get
(
'
src_port
'
),
proto
=
log
.
get
(
'
proto
'
),
dest_ip
=
log
.
get
(
'
dest_ip
'
),
dest_port
=
log
.
get
(
'
dest_port
'
),
orig_data
=
str
(
log
)
if
opts
.
origdata
else
False
,
incident_desription
=
log
[
'
alert
'
][
'
signature
'
])
save_events
(
idea_event
,
filer
)
except
Exception
as
e
:
logging
.
debug
(
e
)
if
not
running_flag
:
break
if
reload_flag
:
for
f
in
files
:
f
.
close
()
f
.
open
()
reload_flag
=
False
if
opts
.
oneshot
:
break
else
:
time
.
sleep
(
opts
.
poll
)
if
__name__
==
"
__main__
"
:
main
()
\ No newline at end of file
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment