Skip to content

Commit f56e058

Browse files
committed
Initial commit
1 parent 24a1820 commit f56e058

File tree

793 files changed

+97351
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

793 files changed

+97351
-0
lines changed

.bowerrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"directory": "app/bower_components"
3+
}

.editorconfig

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# EditorConfig helps developers define and maintain consistent
2+
# coding styles between different editors and IDEs
3+
# editorconfig.org
4+
5+
root = true
6+
7+
8+
[*]
9+
10+
# Change these settings to your own preference
11+
indent_style = space
12+
indent_size = 2
13+
14+
# We recommend you to keep these unchanged
15+
end_of_line = lf
16+
charset = utf-8
17+
trim_trailing_whitespace = true
18+
insert_final_newline = true
19+
20+
[*.md]
21+
trim_trailing_whitespace = false

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
dist
3+
.tmp
4+
.sass-cache
5+
app/bower_components

.jshintrc

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"node": true,
3+
"browser": true,
4+
"esnext": true,
5+
"bitwise": true,
6+
"camelcase": true,
7+
"curly": true,
8+
"eqeqeq": true,
9+
"immed": true,
10+
"indent": 2,
11+
"latedef": true,
12+
"newcap": true,
13+
"noarg": true,
14+
"quotmark": "single",
15+
"regexp": true,
16+
"undef": true,
17+
"unused": true,
18+
"strict": true,
19+
"trailing": true,
20+
"smarttabs": true,
21+
"globals": {
22+
"angular": false
23+
}
24+
}

.travis.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language: node_js
2+
node_js:
3+
- '0.8'
4+
- '0.10'
5+
before_script:
6+
- 'npm install -g bower grunt-cli'
7+
- 'bower install'

LICENSE

Whitespace-only changes.

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

TODO.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Ideas:
2+
3+
Allow user to create 'parsers' regexes that crawl over any result set,
4+
optionally can be limited to type (exportable?). These 'parsers' should
5+
be able to promote user defined IOCs that can then be automatically searched
6+
by other plugins (splunk, sumo, etc)
7+
8+
Allow user to create 'whitelist' of uninterested or known sigs that can be
9+
avoided in order to not waste processing time
10+
11+
Allow user to create 'workflows'. These workflows should allow the user to
12+
specify the whole data collection chain. Including the extraction of IOCs
13+
and retrieval of impact results.

analys-dev.ini

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
[app:main]
2+
use = egg:analys
3+
4+
pyramid.reload_templates = true
5+
pyramid.debug.all = true
6+
pyramid.default_locale_name = en
7+
8+
datastore.host = 127.0.0.1
9+
datastore.port = 27017
10+
message_queue.host = 127.0.0.1
11+
message_queue.port = 6379
12+
13+
14+
[server:main]
15+
use = egg:waitress#main
16+
host = 127.0.0.1
17+
port = 6543
18+
19+
[server:datastore]
20+
host = 127.0.0.1
21+
port = 27017
22+
23+
[server:message_queue]
24+
host = 127.0.0.1
25+
port = 6379
26+
27+
# Begin logging configuration
28+
29+
[loggers]
30+
keys = root, analys
31+
32+
[handlers]
33+
keys = console
34+
35+
[formatters]
36+
keys = generic
37+
38+
[logger_root]
39+
level = INFO
40+
handlers = console
41+
42+
[logger_analys]
43+
level = DEBUG
44+
handlers =
45+
qualname = analys
46+
47+
[handler_console]
48+
class = StreamHandler
49+
args = (sys.stderr,)
50+
level = NOTSET
51+
formatter = generic
52+
53+
[formatter_generic]
54+
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
55+
56+
# End logging configuration

analys.ini

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
[app:main]
2+
use = egg:analys
3+
4+
pyramid.reload_templates = true
5+
pyramid.debug_authorization = false
6+
pyramid.debug_notfound = false
7+
pyramid.debug_routematch = false
8+
pyramid.debug_templates = true
9+
pyramid.default_locale_name = en
10+
11+
datastore.host = 192.168.221.132
12+
datastore.port = 27017
13+
14+
[server:main]
15+
use = egg:waitress#main
16+
host = 192.168.221.132
17+
port = 6543
18+
19+
# Begin logging configuration
20+
21+
[loggers]
22+
keys = root, analys
23+
24+
[handlers]
25+
keys = console
26+
27+
[formatters]
28+
keys = generic
29+
30+
[logger_root]
31+
level = INFO
32+
handlers = console
33+
34+
[logger_analys]
35+
level = DEBUG
36+
handlers =
37+
qualname = analys
38+
39+
[handler_console]
40+
class = StreamHandler
41+
args = (sys.stderr,)
42+
level = NOTSET
43+
formatter = generic
44+
45+
[formatter_generic]
46+
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
47+
48+
# End logging configuration

analys/__init__.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Main entry point
2+
"""
3+
from pyramid.config import Configurator
4+
from pyramid.events import NewRequest
5+
from pyramid.renderers import JSON
6+
7+
from analys.datastore import Datastore
8+
from bson import json_util
9+
from redis import Redis
10+
11+
def main(global_config, **settings):
12+
config = Configurator(settings=settings)
13+
14+
config.include("cornice")
15+
config.scan("analys.views")
16+
config.add_static_view(name='static', path='./static')
17+
18+
ds = Datastore(settings['datastore.host'], settings['datastore.port'])
19+
config.registry.settings['datastore'] = ds
20+
redis = Redis(settings['message_queue.host'], int(settings['message_queue.port']))
21+
config.registry.settings['message_queue'] = redis
22+
23+
config.add_renderer('mongo_json', json_util)
24+
25+
def add_datastore(event):
26+
settings = event.request.registry.settings
27+
datastore = settings['datastore']
28+
event.request.datastore = datastore
29+
30+
def add_message_queue(event):
31+
settings = event.request.registry.settings
32+
message_queue = settings['message_queue']
33+
event.request.message_queue = message_queue
34+
35+
config.add_subscriber(add_message_queue, NewRequest)
36+
config.add_subscriber(add_datastore, NewRequest)
37+
38+
return config.make_wsgi_app()

analys/common/__init__.py

Whitespace-only changes.

analys/common/extractor.py

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
.. module:: extractor
3+
:platform: Unix
4+
:synopsis: A helper module that understands that some zip files submitted
5+
to Analys will be zips that may be password protected.
6+
7+
This module is a helper module that understands that
8+
some zip files submitted to Analys will be zips that may be password
9+
protected. If this is the case Analys will attempt to bruteforce the password.
10+
All files contained inside of the zip will submitted individually to Analys.
11+
12+
.. version:: 1.0
13+
.. moduleauthor:: Kevin Glisson <[email protected]>
14+
"""
15+
import os
16+
import zipfile
17+
import rarfile
18+
import logging
19+
from analys.plugins.interfaces import File
20+
log = logging.getLogger(__file__)
21+
22+
class Extractor(object):
23+
""" Extract attempts to identify the password used to
24+
encrypt a zip file. The most common passwords.
25+
26+
:param data: raw file bytes
27+
:type data: byte string
28+
29+
"""
30+
def __init__(self, file, passwords):
31+
self.file = file
32+
self.samples = []
33+
self.max_depth = 5 #only allow five levels of recursion for compressed files
34+
self.depth = 0
35+
self.passwords = passwords
36+
37+
def extract(self):
38+
if self.depth <= self.max_depth:
39+
if 'zip' in self.file.extension():
40+
with zipfile.ZipFile(self.file.create_temp_file(), 'r') as myzip:
41+
#sanity checks on the file
42+
for member in myzip.namelist():
43+
filename = os.path.basename(member)
44+
if not filename:
45+
continue
46+
47+
try:
48+
sample = myzip.open(member, 'r',).read()
49+
f = File(member, sample)
50+
if f.extension() in ['zip', 'rar']:
51+
self.depth = self.depth + 1
52+
self.extract(f)
53+
else:
54+
self.samples.append(f)
55+
56+
except (zipfile.BadZipfile, RuntimeError) as err:
57+
for password in self.passwords:
58+
59+
try:
60+
sample = myzip.open(member, 'r', password).read()
61+
f = File(member, sample)
62+
if f.extension() in ['zip', 'rar']:
63+
self.depth = self.depth + 1
64+
self.extract(f)
65+
else:
66+
self.samples.append(f)
67+
break
68+
69+
except (zipfile.BadZipfile, RuntimeError) as err:
70+
continue
71+
72+
if 'rar' in self.file.extension():
73+
with rarfile.RarFile(self.file.create_temp_file(), 'r') as myrar:
74+
#sanity checks on the file
75+
for member in myrar.infolist():
76+
if not member.filename:
77+
continue
78+
try:
79+
sample = myrar.open(member, 'r').read()
80+
f = File(member, sample)
81+
if f.extension() in ['zip', 'rar']:
82+
self.depth = self.depth + 1
83+
self.extract(f)
84+
else:
85+
self.samples.append(f)
86+
except:
87+
for password in self.passwords:
88+
try:
89+
sample = myrar.open(member, 'r',
90+
password).read()
91+
f = File(member, sample)
92+
if f.extension() in ['zip', 'rar']:
93+
self.depth = self.depth + 1
94+
self.extract(f)
95+
else:
96+
self.samples.append(f)
97+
break
98+
except:
99+
continue
100+
return self.samples
101+

analys/common/ip.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
.. module: ip
3+
:platform: Unix
4+
:synopsis: This simple module uses the socket module
5+
to attempt to verify a real ip address.
6+
7+
.. version:: 1.0
8+
.. moduleauthor:: Kevin Glisson <[email protected]>
9+
"""
10+
import socket
11+
12+
def is_ipv4(address):
13+
""" Returns True if address is a valid ipv4 address,
14+
returns false otherwise.
15+
16+
"""
17+
try:
18+
addr= socket.inet_pton(socket.AF_INET, address)
19+
except AttributeError: # no inet_pton here, sorry
20+
try:
21+
addr = socket.inet_aton(address)
22+
except socket.error:
23+
return False
24+
return address.count('.') == 3
25+
except socket.error: # not a valid address
26+
return False
27+
28+
return True
29+
30+
def is_ipv6(address):
31+
""" Returns True if address is a valid ipv6 address,
32+
returns false otherwise.
33+
34+
"""
35+
try:
36+
addr= socket.inet_pton(socket.AF_INET6, address)
37+
except socket.error: # not a valid address
38+
return False
39+
return True

0 commit comments

Comments
 (0)