pgsqlpot 2.0.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. core/__init__.py +0 -0
  2. core/config.py +50 -0
  3. core/logfile.py +74 -0
  4. core/output.py +39 -0
  5. core/paths.py +53 -0
  6. core/protocol.py +161 -0
  7. core/tools.py +170 -0
  8. output_plugins/__init__.py +0 -0
  9. output_plugins/couch.py +68 -0
  10. output_plugins/datadog.py +74 -0
  11. output_plugins/discord.py +133 -0
  12. output_plugins/elastic.py +137 -0
  13. output_plugins/hpfeed.py +43 -0
  14. output_plugins/influx2.py +66 -0
  15. output_plugins/jsonlog.py +36 -0
  16. output_plugins/kafka.py +57 -0
  17. output_plugins/localsyslog.py +66 -0
  18. output_plugins/mongodb.py +83 -0
  19. output_plugins/mysql.py +210 -0
  20. output_plugins/nlcvapi.py +119 -0
  21. output_plugins/postgres.py +154 -0
  22. output_plugins/redisdb.py +47 -0
  23. output_plugins/rethinkdblog.py +46 -0
  24. output_plugins/slack.py +94 -0
  25. output_plugins/socketlog.py +40 -0
  26. output_plugins/sqlite.py +141 -0
  27. output_plugins/telegram.py +141 -0
  28. output_plugins/textlog.py +46 -0
  29. output_plugins/xmpp.py +193 -0
  30. pgsqlpot/__init__.py +25 -0
  31. pgsqlpot/cli.py +512 -0
  32. pgsqlpot/data/Dockerfile +56 -0
  33. pgsqlpot/data/docs/INSTALL.md +400 -0
  34. pgsqlpot/data/docs/INSTALLWIN.md +411 -0
  35. pgsqlpot/data/docs/PLUGINS.md +21 -0
  36. pgsqlpot/data/docs/TODO.md +8 -0
  37. pgsqlpot/data/docs/datadog/README.md +32 -0
  38. pgsqlpot/data/docs/discord/README.md +58 -0
  39. pgsqlpot/data/docs/geoipupdtask.ps1 +270 -0
  40. pgsqlpot/data/docs/mysql/README.md +176 -0
  41. pgsqlpot/data/docs/mysql/READMEWIN.md +157 -0
  42. pgsqlpot/data/docs/mysql/mysql.sql +85 -0
  43. pgsqlpot/data/docs/postgres/README.md +184 -0
  44. pgsqlpot/data/docs/postgres/READMEWIN.md +196 -0
  45. pgsqlpot/data/docs/postgres/postgres.sql +73 -0
  46. pgsqlpot/data/docs/slack/README.md +68 -0
  47. pgsqlpot/data/docs/sqlite3/README.md +131 -0
  48. pgsqlpot/data/docs/sqlite3/READMEWIN.md +123 -0
  49. pgsqlpot/data/docs/sqlite3/sqlite3.sql +69 -0
  50. pgsqlpot/data/docs/telegram/README.md +103 -0
  51. pgsqlpot/data/etc/honeypot.cfg +415 -0
  52. pgsqlpot/data/etc/honeypot.cfg.base +418 -0
  53. pgsqlpot/data/test/.gitignore +3 -0
  54. pgsqlpot/data/test/test.py +51 -0
  55. pgsqlpot/honeypot.py +117 -0
  56. pgsqlpot-2.0.0.dist-info/METADATA +152 -0
  57. pgsqlpot-2.0.0.dist-info/RECORD +61 -0
  58. pgsqlpot-2.0.0.dist-info/WHEEL +6 -0
  59. pgsqlpot-2.0.0.dist-info/entry_points.txt +2 -0
  60. pgsqlpot-2.0.0.dist-info/licenses/LICENSE +674 -0
  61. pgsqlpot-2.0.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import absolute_import
5
+
6
+ from sys import exc_info, version_info
7
+
8
+ from core import output
9
+ from core.config import CONFIG
10
+ from core.tools import geolocate
11
+
12
+ from geoip2.database import Reader
13
+
14
+ if version_info[0] < 3:
15
+ import pymysql
16
+ pymysql.install_as_MySQLdb()
17
+
18
+ try:
19
+ from MySQLdb import (Error, OperationalError)
20
+ except ImportError:
21
+ try:
22
+ from MySQLdb._exceptions import (Error, OperationalError)
23
+ except ImportError:
24
+ from _mysql_exceptions import (Error, OperationalError) # type: ignore
25
+
26
+ from twisted.enterprise.adbapi import ConnectionPool
27
+ from twisted.python.compat import reraise
28
+ from twisted.python.log import msg
29
+
30
+
31
+ class ReconnectingConnectionPool(ConnectionPool):
32
+ """
33
+ Reconnecting adbapi connection pool for MySQL.
34
+ This class improves on the solution posted at
35
+ http://www.gelens.org/2008/09/12/reinitializing-twisted-connectionpool/
36
+ by checking exceptions by error code and only disconnecting the current
37
+ connection instead of all of them.
38
+ Also see:
39
+ http://twistedmatrix.com/pipermail/twisted-python/2009-July/020007.html
40
+ """
41
+
42
+ def _runInteraction(self, interaction, *args, **kw):
43
+
44
+ def rerise_exception(conn):
45
+ _, excValue, excTraceback = exc_info()
46
+ try:
47
+ conn.rollback()
48
+ except Exception:
49
+ msg('Rollback failed')
50
+ reraise(excValue, excTraceback)
51
+
52
+ conn = self.connectionFactory(self)
53
+ trans = self.transactionFactory(self, conn)
54
+ try:
55
+ result = interaction(trans, *args, **kw)
56
+ trans.close()
57
+ conn.commit()
58
+ return result
59
+ except OperationalError as e:
60
+ if e.args[0] not in (2003, 2006, 2013):
61
+ rerise_exception(conn)
62
+ else:
63
+ conn = self.connections.get(self.threadID())
64
+ self.disconnect(conn)
65
+ return ConnectionPool._runInteraction(self, interaction, *args, **kw)
66
+ except Exception:
67
+ rerise_exception(conn)
68
+
69
+
70
+ class Output(output.Output):
71
+
72
+ def local_log(self, message):
73
+ if self.debug:
74
+ msg(message)
75
+
76
+ def start(self):
77
+ host = CONFIG.get('output_mysql', 'host', fallback='localhost')
78
+ database = CONFIG.get('output_mysql', 'database', fallback='pgsqlpot')
79
+ user = CONFIG.get('output_mysql', 'username', fallback='pgsqlpot', raw=True)
80
+ password = CONFIG.get('output_mysql', 'password', fallback='', raw=True)
81
+ port = CONFIG.getint('output_mysql', 'port', fallback=3306)
82
+
83
+ self.debug = CONFIG.getboolean('output_mysql', 'debug', fallback=False)
84
+ self.geoip = CONFIG.getboolean('output_mysql', 'geoip', fallback=True)
85
+
86
+ try:
87
+ self.dbh = ReconnectingConnectionPool(
88
+ 'MySQLdb',
89
+ host=host,
90
+ db=database,
91
+ user=user,
92
+ passwd=password,
93
+ port=port,
94
+ charset='utf8',
95
+ use_unicode=True,
96
+ cp_min=1,
97
+ cp_max=1
98
+ )
99
+ except Error as e:
100
+ self.local_log('output_mysql: MySQL Error {}: "{}"'.format(e.args[0], e.args[1]))
101
+
102
+ if self.geoip:
103
+ geoipdb_city_path = CONFIG.get('output_mysql', 'geoip_citydb', fallback='data/GeoLite2-City.mmdb')
104
+ geoipdb_asn_path = CONFIG.get('output_mysql', 'geoip_asndb', fallback='data/GeoLite2-ASN.mmdb')
105
+ try:
106
+ self.reader_city = Reader(geoipdb_city_path)
107
+ except Exception:
108
+ self.reader_city = None
109
+ self.local_log('Failed to open City GeoIP database {}'.format(geoipdb_city_path))
110
+
111
+ try:
112
+ self.reader_asn = Reader(geoipdb_asn_path)
113
+ except Exception:
114
+ self.reader_asn = None
115
+ self.local_log('Failed to open ASN GeoIP database {}'.format(geoipdb_asn_path))
116
+
117
+ def stop(self):
118
+ if self.geoip:
119
+ if self.reader_city is not None:
120
+ self.reader_city.close()
121
+ if self.reader_asn is not None:
122
+ self.reader_asn.close()
123
+
124
+ def write(self, event):
125
+ """
126
+ TODO: Check if the type (date, datetime or timestamp) of columns is appropriate for your needs and timezone
127
+ - MySQL Documentation - The DATE, DATETIME, and TIMESTAMP Types
128
+ (https://dev.mysql.com/doc/refman/5.7/en/datetime.html):
129
+ "MySQL converts TIMESTAMP values from the current time zone to UTC for storage,
130
+ and back from UTC to the current time zone for retrieval.
131
+ (This does not occur for other types such as DATETIME.)"
132
+ """
133
+ self.dbh.runInteraction(self.connect_event, event)
134
+
135
+ def simple_query(self, txn, sql, args):
136
+ if self.debug:
137
+ if len(args):
138
+ self.local_log("output_mysql: MySQL query: {} {}".format(sql, repr(args)))
139
+ else:
140
+ self.local_log("output_mysql: MySQL query: {}".format(sql))
141
+ try:
142
+ if len(args):
143
+ txn.execute(sql, args)
144
+ else:
145
+ txn.execute(sql)
146
+ result = txn.fetchall()
147
+ except Exception as e:
148
+ self.local_log('output_mysql: MySQL Error: {}'.format(e))
149
+ result = None
150
+ return result
151
+
152
+ def get_id(self, txn, table, column, entry):
153
+ r = self.simple_query(txn, "SELECT `id` FROM `{}` WHERE `{}` = %s".format(table, column), (entry, ))
154
+ if r:
155
+ id = r[0][0]
156
+ else:
157
+ self.simple_query(txn, "INSERT INTO `{}` (`{}`) VALUES (%s)".format(table, column), (entry, ))
158
+ r = self.simple_query(txn, 'SELECT LAST_INSERT_ID()', ())
159
+ if r:
160
+ id = int(r[0][0])
161
+ else:
162
+ id = 0
163
+ return id
164
+
165
+ def connect_event(self, txn, event):
166
+ remote_ip = event['src_ip']
167
+ operation_id = self.get_id(txn, 'operations', 'op_name', event['operation'])
168
+ sensor_id = self.get_id(txn, 'sensors', 'name', event['sensor'])
169
+
170
+ self.simple_query(txn,
171
+ """
172
+ INSERT INTO `connections` (
173
+ `session`, `timestamp`, `operation`, `ip`,
174
+ `remote_port`, `local_host`, `local_port`, `sensor`)
175
+ VALUES (%s, FROM_UNIXTIME(%s), %s, %s, %s, %s, %s, %s)
176
+ """,
177
+ (event['session'], event['unixtime'], operation_id, remote_ip,
178
+ event['src_port'], event['dst_ip'], event['dst_port'], sensor_id, ))
179
+
180
+ if event['operation'].lower() == 'login':
181
+ usr_id = self.get_id(txn, 'usernames', 'username', event['username'])
182
+ pwd_id = self.get_id(txn, 'passwords', 'password', event['password'])
183
+ self.simple_query(txn,
184
+ """
185
+ INSERT INTO `credentials` (`session`, `username`, `password`) VALUES(%s, %s, %s)
186
+ """,
187
+ (event['session'], usr_id, pwd_id, ))
188
+ if 'variables' in event:
189
+ for key, value in event['variables'].items():
190
+ var_id = self.get_id(txn, 'vars', 'var_name', key)
191
+ val_id = self.get_id(txn, 'var_values', 'var_value', value)
192
+ self.simple_query(txn,
193
+ """
194
+ INSERT INTO `variables` (`session`, `var`, `val`) VALUES(%s, %s, %s)
195
+ """,
196
+ (event['session'], var_id, val_id, ))
197
+
198
+ if self.geoip:
199
+ country, country_code, city, org, asn_num = geolocate(remote_ip, self.reader_city, self.reader_asn)
200
+ self.simple_query(txn, """
201
+ INSERT INTO `geolocation` (`ip`, `country_name`, `country_iso_code`, `city_name`, `org`, `org_asn`)
202
+ VALUES (%s, %s, %s, %s, %s, %s)
203
+ ON DUPLICATE KEY UPDATE
204
+ `country_name` = %s,
205
+ `country_iso_code` = %s,
206
+ `city_name` = %s,
207
+ `org` = %s,
208
+ `org_asn` = %s
209
+ """,
210
+ (remote_ip, country, country_code, city, org, asn_num, country, country_code, city, org, asn_num, ))
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import absolute_import
5
+
6
+ from io import BytesIO
7
+ from json import dumps, loads
8
+
9
+ from core import output
10
+ from core.config import CONFIG
11
+ from core.tools import decode, geolocate, to_bytes
12
+
13
+ from geoip2.database import Reader
14
+
15
+ from twisted.internet import reactor
16
+ from twisted.internet.ssl import ClientContextFactory
17
+ from twisted.python.log import msg
18
+ from twisted.web.client import (
19
+ Agent,
20
+ FileBodyProducer,
21
+ HTTPConnectionPool,
22
+ _HTTP11ClientFactory,
23
+ readBody,
24
+ )
25
+ from twisted.web.http_headers import Headers
26
+
27
+
28
+ class WebClientContextFactory(ClientContextFactory):
29
+
30
+ def getContext(self, hostname, port):
31
+ return ClientContextFactory.getContext(self)
32
+
33
+
34
+ class QuietHTTP11ClientFactory(_HTTP11ClientFactory):
35
+ noisy = False
36
+
37
+
38
+ class Output(output.Output):
39
+
40
+ def start(self):
41
+ self.host = CONFIG.get('output_nlcvapi', 'host', fallback='https://api.nlcv.bas.bg/v1.0/honeypot')
42
+ self.geoip = CONFIG.getboolean('output_nlcvapi', 'geoip', fallback=True)
43
+ contextFactory = WebClientContextFactory()
44
+ pool = HTTPConnectionPool(reactor)
45
+ pool._factory = QuietHTTP11ClientFactory
46
+ self.agent = Agent(reactor, contextFactory=contextFactory, pool=pool)
47
+
48
+ if self.geoip:
49
+ geoipdb_city_path = CONFIG.get('output_nlcvapi', 'geoip_citydb', fallback='data/GeoLite2-City.mmdb')
50
+ geoipdb_asn_path = CONFIG.get('output_nlcvapi', 'geoip_asndb', fallback='data/GeoLite2-ASN.mmdb')
51
+ try:
52
+ self.reader_city = Reader(geoipdb_city_path)
53
+ except Exception:
54
+ self.reader_city = None
55
+ msg('Failed to open City GeoIP database {}'.format(geoipdb_city_path))
56
+ try:
57
+ self.reader_asn = Reader(geoipdb_asn_path)
58
+ except Exception:
59
+ self.reader_asn = None
60
+ msg('Failed to open ASN GeoIP database {}'.format(geoipdb_asn_path))
61
+
62
+ def stop(self):
63
+ if self.geoip:
64
+ if getattr(self, 'reader_city', None) is not None:
65
+ self.reader_city.close()
66
+ if getattr(self, 'reader_asn', None) is not None:
67
+ self.reader_asn.close()
68
+
69
+ def write(self, event):
70
+ event['honeypot'] = 'pgsqlpot'
71
+ if self.geoip:
72
+ country, country_code, city, org, asn_num = geolocate(event['src_ip'], self.reader_city, self.reader_asn)
73
+ event['country'] = country
74
+ event['country_code'] = country_code
75
+ event['city'] = city
76
+ event['org'] = org
77
+ event['asn'] = asn_num
78
+ self._postentry(event)
79
+
80
+ def _postentry(self, entry):
81
+
82
+ def cbBody(body):
83
+ return processResult(body)
84
+
85
+ def cbPartial(failure):
86
+ """
87
+ Google HTTP Server does not set Content-Length. Twisted marks it as partial
88
+ """
89
+ failure.printTraceback()
90
+ return processResult(failure.value.response)
91
+
92
+ def cbResponse(response):
93
+ if response.code in (200, 201):
94
+ return
95
+ msg('NLCVAPI response: {}: "{}"'.format(response.code, decode(response.phrase)))
96
+ d = readBody(response)
97
+ d.addCallback(cbBody)
98
+ d.addErrback(cbPartial)
99
+ return d
100
+
101
+ def cbError(failure):
102
+ failure.printTraceback()
103
+
104
+ def processResult(result):
105
+ try:
106
+ j = loads(result)
107
+ msg('NLCVAPI response: {}'.format(j.get('message', '')))
108
+ except Exception:
109
+ pass
110
+
111
+ headers = Headers({
112
+ b'User-Agent': [b'PGSQLPot'],
113
+ b'Content-Type': [b'application/json'],
114
+ })
115
+ body = FileBodyProducer(BytesIO(to_bytes(dumps(entry, sort_keys=True))))
116
+ d = self.agent.request(b'POST', to_bytes(self.host), headers, body)
117
+ d.addCallback(cbResponse)
118
+ d.addErrback(cbError)
119
+ return d
@@ -0,0 +1,154 @@
1
+
2
+ from __future__ import absolute_import
3
+
4
+ from core import output
5
+ from core.config import CONFIG
6
+ from core.tools import geolocate
7
+
8
+ from geoip2.database import Reader
9
+ from psycopg2 import OperationalError
10
+
11
+ from twisted.enterprise.adbapi import ConnectionPool
12
+ from twisted.python.log import msg
13
+
14
+
15
+ class Output(output.Output):
16
+
17
+ def start(self):
18
+ host = CONFIG.get('output_postgres', 'host', fallback='localhost')
19
+ port = CONFIG.getint('output_postgres', 'port', fallback=5432)
20
+ username = CONFIG.get('output_postgres', 'username', fallback='pgsqlpot')
21
+ password = CONFIG.get('output_postgres', 'password')
22
+ database = CONFIG.get('output_postgres', 'database', fallback='pgsqlpot')
23
+ self.debug = CONFIG.getboolean('output_postgres', 'debug', fallback=False)
24
+ self.geoip = CONFIG.getboolean('output_postgres', 'geoip', fallback=True)
25
+
26
+ try:
27
+ self.dbh = ConnectionPool(
28
+ 'psycopg2',
29
+ database=database,
30
+ user=username,
31
+ password=password,
32
+ host=host,
33
+ port=port,
34
+ cp_min=1,
35
+ cp_max=1
36
+ )
37
+ except OperationalError as e:
38
+ msg('output_postgres: postgres Error: {}'.format(e))
39
+
40
+ if self.geoip:
41
+ geoipdb_city_path = CONFIG.get('output_postgres', 'geoip_citydb', fallback='data/GeoLite2-City.mmdb')
42
+ geoipdb_asn_path = CONFIG.get('output_postgres', 'geoip_asndb', fallback='data/GeoLite2-ASN.mmdb')
43
+ try:
44
+ self.reader_city = Reader(geoipdb_city_path)
45
+ except Exception:
46
+ self.reader_city = None
47
+ msg('Failed to open City GeoIP database {}'.format(geoipdb_city_path))
48
+ try:
49
+ self.reader_asn = Reader(geoipdb_asn_path)
50
+ except Exception:
51
+ self.reader_asn = None
52
+ msg('Failed to open ASN GeoIP database {}'.format(geoipdb_asn_path))
53
+
54
+ def stop(self):
55
+ if self.geoip:
56
+ if self.reader_city is not None:
57
+ self.reader_city.close()
58
+ if self.reader_asn is not None:
59
+ self.reader_asn.close()
60
+
61
+ def write(self, event):
62
+ self.dbh.runInteraction(self.connect_event, event)
63
+
64
+ def simple_query(self, txn, sql, args, returns_value=True):
65
+ if self.debug:
66
+ msg('output_postgres: postgres query: {} {}'.format(sql, repr(args)))
67
+ result = None
68
+ try:
69
+ txn.execute(sql, args)
70
+ if returns_value:
71
+ result = txn.fetchone()
72
+ except Exception as e:
73
+ msg('output_postgres: postgres Error: {}'.format(e))
74
+ return result
75
+
76
+ def get_id(self, txn, table, column, entry):
77
+ r = self.simple_query(
78
+ txn,
79
+ "SELECT id FROM {} WHERE {} = %s".format(table, column),
80
+ (entry, )
81
+ )
82
+ if r:
83
+ id = int(r[0])
84
+ else:
85
+ r = self.simple_query(
86
+ txn,
87
+ "INSERT INTO {} ({}) VALUES (%s) RETURNING id".format(table, column),
88
+ (entry, )
89
+ )
90
+ if r:
91
+ id = int(r[0])
92
+ else:
93
+ id = 0
94
+ return id
95
+
96
+ def connect_event(self, txn, event):
97
+ remote_ip = event['src_ip']
98
+ operation_id = self.get_id(txn, 'operations', 'op_name', event['operation'])
99
+ sensor_id = self.get_id(txn, 'sensors', 'sname', event['sensor'])
100
+
101
+ self.simple_query(
102
+ txn,
103
+ """
104
+ INSERT INTO connections (
105
+ sess_no, time_stamp, ip, remote_port, operation,
106
+ local_host, local_port, sensor)
107
+ VALUES (%s, to_timestamp(%s), %s, %s, %s, %s, %s, %s)
108
+ """,
109
+ (event['session'], event['unixtime'], remote_ip, event['src_port'], operation_id,
110
+ event['dst_ip'], event['dst_port'], sensor_id, ),
111
+ False
112
+ )
113
+
114
+ if event['operation'].lower() == 'login':
115
+ usr_id = self.get_id(txn, 'usernames', 'username', event['username'])
116
+ pwd_id = self.get_id(txn, 'passwords', 'passwd', event['password'])
117
+ self.simple_query(
118
+ txn,
119
+ """
120
+ INSERT INTO credentials (sess_no, username, passwd) VALUES(%s, %s, %s)
121
+ """,
122
+ (event['session'], usr_id, pwd_id, ),
123
+ False
124
+ )
125
+ if 'variables' in event:
126
+ for key, value in event['variables'].items():
127
+ var_id = self.get_id(txn, 'vars', 'var_name', key)
128
+ val_id = self.get_id(txn, 'var_values', 'var_value', value)
129
+ self.simple_query(
130
+ txn,
131
+ """
132
+ INSERT INTO variables (sess_no, var, val) VALUES(%s, %s, %s)
133
+ """,
134
+ (event['session'], var_id, val_id, ),
135
+ False
136
+ )
137
+
138
+ if self.geoip:
139
+ country, country_code, city, org, asn_num = geolocate(remote_ip, self.reader_city, self.reader_asn)
140
+ self.simple_query(
141
+ txn,
142
+ """
143
+ INSERT INTO geolocation (ip, country_name, country_iso_code, city_name, org, org_asn)
144
+ VALUES (%s, %s, %s, %s, %s, %s)
145
+ ON CONFLICT (ip) DO UPDATE SET
146
+ country_name = EXCLUDED.country_name,
147
+ country_iso_code = EXCLUDED.country_iso_code,
148
+ city_name = EXCLUDED.city_name,
149
+ org = EXCLUDED.org,
150
+ org_asn = EXCLUDED.org_asn
151
+ """,
152
+ (remote_ip, country, country_code, city, org, asn_num, ),
153
+ False
154
+ )
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import absolute_import
5
+
6
+ from json import dumps
7
+
8
+ from core import output
9
+ from core.config import CONFIG
10
+
11
+ from requests import post
12
+
13
+ from twisted.internet.threads import deferToThread
14
+ from twisted.python.log import msg
15
+
16
+ class Output(output.Output):
17
+
18
+ def start(self):
19
+ self.debug = CONFIG.getboolean('output_redisdb', 'debug', fallback=False)
20
+ host = CONFIG.get('output_redisdb', 'host')
21
+ self.password = CONFIG.get('output_redisdb', 'password')
22
+ keyname = CONFIG.get('output_redisdb', 'keyname', fallback='pgsqlpot')
23
+ self.url = 'https://{}/lpush/{}'.format(host, keyname)
24
+
25
+ def stop(self):
26
+ pass
27
+
28
+ def write(self, event):
29
+ payload = dumps(event, sort_keys=True)
30
+ headers = {
31
+ b'Authorization': 'Bearer {}'.format(self.password),
32
+ b'Content-Type': b'application/json'
33
+ }
34
+
35
+ if self.debug:
36
+ msg(payload)
37
+
38
+ d = deferToThread(post, self.url, data=payload, headers=headers, timeout=5)
39
+ d.addCallback(self._on_response)
40
+ d.addErrback(self._on_error)
41
+
42
+ def _on_response(self, response):
43
+ if self.debug and response.status_code != 200:
44
+ msg('[REST] Response code: {}'.format(response.status_code))
45
+
46
+ def _on_error(self, failure):
47
+ msg('[REST] REST error: {}'.format(failure))
@@ -0,0 +1,46 @@
1
+
2
+ from __future__ import absolute_import
3
+
4
+ from time import sleep
5
+
6
+ from core import output
7
+ from core.config import CONFIG
8
+
9
+ from rethinkdb import r
10
+
11
+ from twisted.python.log import msg
12
+
13
+
14
+ class Output(output.Output):
15
+
16
+ def start(self):
17
+ host = CONFIG.get('output_rethinkdblog', 'host', fallback='localhost')
18
+ port = CONFIG.getint('output_rethinkdblog', 'port', fallback=28015)
19
+ self.db = CONFIG.get('output_rethinkdblog', 'db', fallback='pgsqlpot')
20
+ password = CONFIG.get('output_rethinkdblog', 'password', raw=True)
21
+ self.table = CONFIG.get('output_rethinkdblog', 'table', fallback='events')
22
+
23
+ try:
24
+ self.connection = r.connect(host=host, port=port, password=password)
25
+
26
+ if self.db not in r.db_list().run(self.connection):
27
+ r.db_create(self.db).run(self.connection)
28
+ for _ in range(50): # ~5 seconds max
29
+ if self.db in r.db_list().run(self.connection):
30
+ break
31
+ sleep(0.1)
32
+ if self.table not in r.db(self.db).table_list().run(self.connection):
33
+ r.db(self.db).table_create(self.table).run(self.connection)
34
+
35
+ except r.RqlError as err:
36
+ msg('output_rethinkdblog: Error: {}'.format(err.message))
37
+
38
+ def stop(self):
39
+ if getattr(self, 'connection', None):
40
+ self.connection.close()
41
+
42
+ def write(self, event):
43
+ try:
44
+ r.db(self.db).table(self.table).insert(event).run(self.connection)
45
+ except r.RqlRuntimeError as err:
46
+ msg('output_rethinkdblog: Error: {}'.format(err.message))
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import absolute_import
5
+
6
+ from sys import version_info
7
+ from time import gmtime, strftime, time
8
+
9
+ from core import output
10
+ from core.config import CONFIG
11
+
12
+ from twisted.internet import reactor
13
+ from twisted.internet.task import deferLater
14
+
15
+ # --- Slack compatibility wrapper ---
16
+ if version_info[0] >= 3:
17
+ # Python 3: slack-sdk
18
+ from slack_sdk import WebClient as SlackClientWrapper # type: ignore
19
+ else:
20
+ # Python 2.7: slackclient 1.x
21
+ from slackclient import SlackClient as BaseSlackClient # type: ignore
22
+
23
+ class SlackResponse(object):
24
+ """Wrap Python 2 SlackClient dict to mimic SlackResponse"""
25
+ def __init__(self, data):
26
+ self.data = data
27
+
28
+ class SlackClientWrapper(object):
29
+ def __init__(self, token):
30
+ self._client = BaseSlackClient(token)
31
+
32
+ def chat_postMessage(self, **kwargs):
33
+ result = self._client.api_call("chat.postMessage", **kwargs)
34
+ return SlackResponse(result)
35
+
36
+
37
+ class Output(output.Output):
38
+ """
39
+ Slack webhook output plugin.
40
+ """
41
+
42
+ def start(self):
43
+ self.slack_channel = CONFIG.get('output_slack', 'channel')
44
+ self.slack_token = CONFIG.get('output_slack', 'token')
45
+ self.delay = CONFIG.getfloat('output_slack', 'delay', fallback=1.2)
46
+ self.sc = SlackClientWrapper(self.slack_token)
47
+ self.last_sent = 0
48
+ self.requests_list = []
49
+
50
+ def stop(self):
51
+ pass
52
+
53
+ def write(self, event):
54
+ operation = event['operation'].lower()
55
+
56
+ message = '[{} UTC] [PGSQLPot on {} ({})]: {}'.format(
57
+ strftime('%Y-%m-%d %H:%M:%S', gmtime(event['unixtime'])),
58
+ event['sensor'], event['session'], operation.capitalize()
59
+ )
60
+ if operation == 'unknown':
61
+ message += ' operation: "{}"'.format(event['username'])
62
+ elif operation == 'command':
63
+ message += ' "{}'.format(event['command'])
64
+ if event['args']:
65
+ message += ' {}'.format(event['args'])
66
+ message += '"'
67
+ message += ' from {}:{}'.format(event['src_ip'], event['dst_port'])
68
+ if operation == 'login':
69
+ message += ', username: "{}", password: "{}"'.format(
70
+ event['username'], event['password']
71
+ )
72
+ message += '.'
73
+
74
+ self.requests_list.append(message)
75
+ self._drain()
76
+
77
+ def _drain(self):
78
+ """
79
+ Send the next queued message if the rate-limit window has elapsed.
80
+ If messages remain, schedule another drain after self.delay seconds.
81
+ """
82
+ if not self.requests_list:
83
+ return
84
+ now = time()
85
+ elapsed = now - self.last_sent
86
+ if elapsed < self.delay:
87
+ # Re-schedule after the remainder of the delay window
88
+ deferLater(reactor, self.delay - elapsed, self._drain)
89
+ return
90
+ self.last_sent = now
91
+ message = self.requests_list.pop(0)
92
+ self.sc.chat_postMessage(channel=self.slack_channel, text=message)
93
+ if self.requests_list:
94
+ deferLater(reactor, self.delay, self._drain)