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.
- core/__init__.py +0 -0
- core/config.py +50 -0
- core/logfile.py +74 -0
- core/output.py +39 -0
- core/paths.py +53 -0
- core/protocol.py +161 -0
- core/tools.py +170 -0
- output_plugins/__init__.py +0 -0
- output_plugins/couch.py +68 -0
- output_plugins/datadog.py +74 -0
- output_plugins/discord.py +133 -0
- output_plugins/elastic.py +137 -0
- output_plugins/hpfeed.py +43 -0
- output_plugins/influx2.py +66 -0
- output_plugins/jsonlog.py +36 -0
- output_plugins/kafka.py +57 -0
- output_plugins/localsyslog.py +66 -0
- output_plugins/mongodb.py +83 -0
- output_plugins/mysql.py +210 -0
- output_plugins/nlcvapi.py +119 -0
- output_plugins/postgres.py +154 -0
- output_plugins/redisdb.py +47 -0
- output_plugins/rethinkdblog.py +46 -0
- output_plugins/slack.py +94 -0
- output_plugins/socketlog.py +40 -0
- output_plugins/sqlite.py +141 -0
- output_plugins/telegram.py +141 -0
- output_plugins/textlog.py +46 -0
- output_plugins/xmpp.py +193 -0
- pgsqlpot/__init__.py +25 -0
- pgsqlpot/cli.py +512 -0
- pgsqlpot/data/Dockerfile +56 -0
- pgsqlpot/data/docs/INSTALL.md +400 -0
- pgsqlpot/data/docs/INSTALLWIN.md +411 -0
- pgsqlpot/data/docs/PLUGINS.md +21 -0
- pgsqlpot/data/docs/TODO.md +8 -0
- pgsqlpot/data/docs/datadog/README.md +32 -0
- pgsqlpot/data/docs/discord/README.md +58 -0
- pgsqlpot/data/docs/geoipupdtask.ps1 +270 -0
- pgsqlpot/data/docs/mysql/README.md +176 -0
- pgsqlpot/data/docs/mysql/READMEWIN.md +157 -0
- pgsqlpot/data/docs/mysql/mysql.sql +85 -0
- pgsqlpot/data/docs/postgres/README.md +184 -0
- pgsqlpot/data/docs/postgres/READMEWIN.md +196 -0
- pgsqlpot/data/docs/postgres/postgres.sql +73 -0
- pgsqlpot/data/docs/slack/README.md +68 -0
- pgsqlpot/data/docs/sqlite3/README.md +131 -0
- pgsqlpot/data/docs/sqlite3/READMEWIN.md +123 -0
- pgsqlpot/data/docs/sqlite3/sqlite3.sql +69 -0
- pgsqlpot/data/docs/telegram/README.md +103 -0
- pgsqlpot/data/etc/honeypot.cfg +415 -0
- pgsqlpot/data/etc/honeypot.cfg.base +418 -0
- pgsqlpot/data/test/.gitignore +3 -0
- pgsqlpot/data/test/test.py +51 -0
- pgsqlpot/honeypot.py +117 -0
- pgsqlpot-2.0.0.dist-info/METADATA +152 -0
- pgsqlpot-2.0.0.dist-info/RECORD +61 -0
- pgsqlpot-2.0.0.dist-info/WHEEL +6 -0
- pgsqlpot-2.0.0.dist-info/entry_points.txt +2 -0
- pgsqlpot-2.0.0.dist-info/licenses/LICENSE +674 -0
- pgsqlpot-2.0.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import absolute_import
|
|
3
|
+
|
|
4
|
+
from json import dumps
|
|
5
|
+
from socket import socket, AF_INET, SOCK_STREAM
|
|
6
|
+
|
|
7
|
+
from core import output
|
|
8
|
+
from core.config import CONFIG
|
|
9
|
+
from core.tools import encode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Output(output.Output):
|
|
13
|
+
"""
|
|
14
|
+
socketlog output
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def start(self):
|
|
18
|
+
timeout = CONFIG.getint('output_socketlog', 'timeout')
|
|
19
|
+
addr = CONFIG.get('output_socketlog', 'address')
|
|
20
|
+
host = addr.split(':')[0]
|
|
21
|
+
port = int(addr.split(':')[1])
|
|
22
|
+
|
|
23
|
+
self.sock = socket(AF_INET, SOCK_STREAM)
|
|
24
|
+
self.sock.settimeout(timeout)
|
|
25
|
+
self.sock.connect((host, port))
|
|
26
|
+
|
|
27
|
+
def stop(self):
|
|
28
|
+
self.sock.close()
|
|
29
|
+
|
|
30
|
+
def write(self, event):
|
|
31
|
+
message = dumps(event, sort_keys=True) + '\n'
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
self.sock.sendall(encode(message))
|
|
35
|
+
except OSError as ex:
|
|
36
|
+
if ex.errno == 32: # Broken pipe
|
|
37
|
+
self.start()
|
|
38
|
+
self.sock.sendall(encode(message))
|
|
39
|
+
else:
|
|
40
|
+
raise
|
output_plugins/sqlite.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
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 sqlite3 import OperationalError, IntegrityError
|
|
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
|
+
db_name = CONFIG.get('output_sqlite', 'db_file', fallback='data/pgsqlpot.db')
|
|
19
|
+
self.debug = CONFIG.getboolean('output_sqlite', 'debug', fallback=False)
|
|
20
|
+
self.geoip = CONFIG.getboolean('output_sqlite', 'geoip', fallback=True)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
self.db = ConnectionPool(
|
|
24
|
+
'sqlite3',
|
|
25
|
+
database=db_name,
|
|
26
|
+
check_same_thread=False
|
|
27
|
+
)
|
|
28
|
+
except OperationalError as e:
|
|
29
|
+
msg(e)
|
|
30
|
+
|
|
31
|
+
self.db.start()
|
|
32
|
+
|
|
33
|
+
if self.geoip:
|
|
34
|
+
geoipdb_city_path = CONFIG.get('output_sqlite', 'geoip_citydb', fallback='data/GeoLite2-City.mmdb')
|
|
35
|
+
geoipdb_asn_path = CONFIG.get('output_sqlite', 'geoip_asndb', fallback='data/GeoLite2-ASN.mmdb')
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
self.reader_city = Reader(geoipdb_city_path)
|
|
39
|
+
except Exception:
|
|
40
|
+
self.reader_city = None
|
|
41
|
+
msg('Failed to open City GeoIP database {}'.format(geoipdb_city_path))
|
|
42
|
+
try:
|
|
43
|
+
self.reader_asn = Reader(geoipdb_asn_path)
|
|
44
|
+
except Exception:
|
|
45
|
+
self.reader_asn = None
|
|
46
|
+
msg('Failed to open ASN GeoIP database {}'.format(geoipdb_asn_path))
|
|
47
|
+
|
|
48
|
+
def stop(self):
|
|
49
|
+
if self.geoip:
|
|
50
|
+
if self.reader_city is not None:
|
|
51
|
+
self.reader_city.close()
|
|
52
|
+
if self.reader_asn is not None:
|
|
53
|
+
self.reader_asn.close()
|
|
54
|
+
|
|
55
|
+
def write(self, event):
|
|
56
|
+
self.db.runInteraction(self.connect_event, event)
|
|
57
|
+
|
|
58
|
+
def simple_query(self, txn, sql, args):
|
|
59
|
+
if self.debug:
|
|
60
|
+
if len(args):
|
|
61
|
+
msg('output_sqlite: SQLite3 query: {} {}'.format(sql, repr(args)))
|
|
62
|
+
else:
|
|
63
|
+
msg('output_sqlite: SQLite3 query: {}'.format(sql))
|
|
64
|
+
try:
|
|
65
|
+
if len(args):
|
|
66
|
+
txn.execute(sql, args)
|
|
67
|
+
else:
|
|
68
|
+
txn.execute(sql)
|
|
69
|
+
result = txn.fetchall()
|
|
70
|
+
except IntegrityError as e:
|
|
71
|
+
result = None
|
|
72
|
+
except Exception as e:
|
|
73
|
+
msg('output_sqlite: SQLite3 Error: {}'.format(e))
|
|
74
|
+
result = None
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
def get_id(self, txn, table, column, entry):
|
|
78
|
+
r = self.simple_query(txn, "SELECT `id` FROM `{}` WHERE `{}` = ?".format(table, column), (entry, ))
|
|
79
|
+
if r:
|
|
80
|
+
id = r[0][0]
|
|
81
|
+
else:
|
|
82
|
+
self.simple_query(txn, "INSERT INTO `{}` (`{}`) VALUES (?)".format(table, column), (entry, ))
|
|
83
|
+
r = self.simple_query(txn, 'SELECT LAST_INSERT_ROWID()', ())
|
|
84
|
+
if r:
|
|
85
|
+
id = int(r[0][0])
|
|
86
|
+
else:
|
|
87
|
+
id = 0
|
|
88
|
+
return id
|
|
89
|
+
|
|
90
|
+
def connect_event(self, txn, event):
|
|
91
|
+
remote_ip = event['src_ip']
|
|
92
|
+
|
|
93
|
+
operation_id = self.get_id(txn, 'operations', 'op_name', event['operation'])
|
|
94
|
+
sensor_id = self.get_id(txn, 'sensors', 'name', event['sensor'])
|
|
95
|
+
|
|
96
|
+
self.simple_query(txn,
|
|
97
|
+
"""
|
|
98
|
+
INSERT INTO `connections` (`session`, `timestamp`, `operation`, `ip`, `remote_port`, `local_host`, `local_port`, `sensor`)
|
|
99
|
+
VALUES (?, DATETIME(?, 'unixepoch', 'localtime'), ?, ?, ?, ?, ?, ?)
|
|
100
|
+
""",
|
|
101
|
+
(event['session'], event['unixtime'], operation_id, remote_ip, event['src_port'],
|
|
102
|
+
event['dst_ip'], event['dst_port'], sensor_id, ))
|
|
103
|
+
|
|
104
|
+
if event['operation'].lower() == 'login':
|
|
105
|
+
usr_id = self.get_id(txn, 'usernames', 'username', event['username'])
|
|
106
|
+
pwd_id = self.get_id(txn, 'passwords', 'password', event['password'])
|
|
107
|
+
self.simple_query(txn,
|
|
108
|
+
"""
|
|
109
|
+
INSERT INTO `credentials` (`session`, `username`, `password`) VALUES(?, ?, ?)
|
|
110
|
+
""",
|
|
111
|
+
(event['session'], usr_id, pwd_id, ))
|
|
112
|
+
if 'variables' in event:
|
|
113
|
+
for key, value in event['variables'].items():
|
|
114
|
+
var_id = self.get_id(txn, 'vars', 'var_name', key)
|
|
115
|
+
val_id = self.get_id(txn, 'var_values', 'var_value', value)
|
|
116
|
+
self.simple_query(txn,
|
|
117
|
+
"""
|
|
118
|
+
INSERT INTO `variables` (`session`, `var`, `val`) VALUES(?, ?, ?)
|
|
119
|
+
""",
|
|
120
|
+
(event['session'], var_id, val_id, ))
|
|
121
|
+
|
|
122
|
+
if self.geoip:
|
|
123
|
+
country, country_code, city, org, asn_num = geolocate(remote_ip, self.reader_city, self.reader_asn)
|
|
124
|
+
result = self.simple_query(txn,
|
|
125
|
+
"""
|
|
126
|
+
INSERT INTO `geolocation` (`ip`, `country_name`, `country_iso_code`, `city_name`, `org`, `org_asn`)
|
|
127
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
128
|
+
""",
|
|
129
|
+
(remote_ip, country, country_code, city, org, asn_num, ))
|
|
130
|
+
if result is None:
|
|
131
|
+
self.simple_query(txn,
|
|
132
|
+
"""
|
|
133
|
+
UPDATE `geolocation` SET
|
|
134
|
+
`country_name` = ?,
|
|
135
|
+
`country_iso_code` = ?,
|
|
136
|
+
`city_name` = ?,
|
|
137
|
+
`org` = ?,
|
|
138
|
+
`org_asn` = ?
|
|
139
|
+
WHERE `ip` = ?
|
|
140
|
+
""",
|
|
141
|
+
(country, country_code, city, org, asn_num, remote_ip, ))
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Simple Telegram Bot logger
|
|
5
|
+
|
|
6
|
+
from __future__ import absolute_import
|
|
7
|
+
|
|
8
|
+
from json import loads
|
|
9
|
+
from time import gmtime, strftime, time
|
|
10
|
+
|
|
11
|
+
from core import output
|
|
12
|
+
from core.config import CONFIG
|
|
13
|
+
from core.tools import decode, encode
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from urllib import quote_plus
|
|
17
|
+
except ImportError:
|
|
18
|
+
from urllib.parse import quote_plus
|
|
19
|
+
|
|
20
|
+
from twisted.internet import reactor
|
|
21
|
+
from twisted.internet.ssl import ClientContextFactory
|
|
22
|
+
from twisted.internet.task import deferLater
|
|
23
|
+
from twisted.python.log import msg
|
|
24
|
+
from twisted.web.client import (
|
|
25
|
+
Agent,
|
|
26
|
+
HTTPConnectionPool,
|
|
27
|
+
_HTTP11ClientFactory,
|
|
28
|
+
readBody
|
|
29
|
+
)
|
|
30
|
+
from twisted.web.http_headers import Headers
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WebClientContextFactory(ClientContextFactory):
|
|
34
|
+
|
|
35
|
+
def getContext(self, hostname, port):
|
|
36
|
+
return ClientContextFactory.getContext(self)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class QuietHTTP11ClientFactory(_HTTP11ClientFactory):
|
|
40
|
+
noisy = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Output(output.Output):
|
|
44
|
+
"""
|
|
45
|
+
Telegram bot output plugin.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def start(self):
|
|
49
|
+
self.bot_token = CONFIG.get('output_telegram', 'bot_token')
|
|
50
|
+
self.chat_id = CONFIG.get('output_telegram', 'chat_id')
|
|
51
|
+
self.delay = CONFIG.getfloat('output_telegram', 'delay', fallback=2.0)
|
|
52
|
+
|
|
53
|
+
contextFactory = WebClientContextFactory()
|
|
54
|
+
pool = HTTPConnectionPool(reactor)
|
|
55
|
+
pool._factory = QuietHTTP11ClientFactory
|
|
56
|
+
self.agent = Agent(reactor, contextFactory=contextFactory, pool=pool)
|
|
57
|
+
self.last_sent = 0
|
|
58
|
+
self.requests_list = []
|
|
59
|
+
|
|
60
|
+
def stop(self):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def write(self, event):
|
|
64
|
+
operation = event['operation'].lower()
|
|
65
|
+
message = '[{} UTC] [PGSQLPot on {} ({})]: {}'.format(
|
|
66
|
+
strftime('%Y-%m-%d %H:%M:%S', gmtime(event['unixtime'])),
|
|
67
|
+
event['sensor'], event['session'], operation.capitalize()
|
|
68
|
+
)
|
|
69
|
+
if operation == 'unknown':
|
|
70
|
+
message += ' operation: "{}"'.format(event['username'])
|
|
71
|
+
elif operation == 'command':
|
|
72
|
+
message += ' "{}'.format(event['command'])
|
|
73
|
+
if event['args']:
|
|
74
|
+
message += ' {}'.format(event['args'])
|
|
75
|
+
message += '"'
|
|
76
|
+
message += ' from {}:{}'.format(event['src_ip'], event['dst_port'])
|
|
77
|
+
if operation == 'login':
|
|
78
|
+
message += ', username: "{}", password: "{}"'.format(
|
|
79
|
+
event['username'], event['password']
|
|
80
|
+
)
|
|
81
|
+
message += '.'
|
|
82
|
+
self.requests_list.append(message)
|
|
83
|
+
self._drain()
|
|
84
|
+
|
|
85
|
+
def _drain(self):
|
|
86
|
+
"""Send the next queued message if the rate-limit window has elapsed.
|
|
87
|
+
If messages remain, schedule another drain after self.delay seconds."""
|
|
88
|
+
if not self.requests_list:
|
|
89
|
+
return
|
|
90
|
+
now = time()
|
|
91
|
+
elapsed = now - self.last_sent
|
|
92
|
+
if elapsed < self.delay:
|
|
93
|
+
deferLater(reactor, self.delay - elapsed, self._drain)
|
|
94
|
+
return
|
|
95
|
+
self.last_sent = now
|
|
96
|
+
message = self.requests_list.pop(0)
|
|
97
|
+
d = self._send(message)
|
|
98
|
+
if self.requests_list:
|
|
99
|
+
deferLater(reactor, self.delay, self._drain)
|
|
100
|
+
return d
|
|
101
|
+
|
|
102
|
+
def _send(self, message):
|
|
103
|
+
|
|
104
|
+
def cbBody(body):
|
|
105
|
+
return processResult(body)
|
|
106
|
+
|
|
107
|
+
def cbPartial(failure):
|
|
108
|
+
"""
|
|
109
|
+
Google HTTP Server does not set Content-Length. Twisted marks it as partial
|
|
110
|
+
"""
|
|
111
|
+
failure.printTraceback()
|
|
112
|
+
return processResult(failure.value.response)
|
|
113
|
+
|
|
114
|
+
def cbResponse(response):
|
|
115
|
+
if response.code in [200, 201]:
|
|
116
|
+
return
|
|
117
|
+
msg('Telegram error: {} {}'.format(response.code, decode(response.phrase)))
|
|
118
|
+
d = readBody(response)
|
|
119
|
+
d.addCallback(cbBody)
|
|
120
|
+
d.addErrback(cbPartial)
|
|
121
|
+
return d
|
|
122
|
+
|
|
123
|
+
def cbError(failure):
|
|
124
|
+
failure.printTraceback()
|
|
125
|
+
|
|
126
|
+
def processResult(result):
|
|
127
|
+
try:
|
|
128
|
+
j = loads(result)
|
|
129
|
+
msg('Telegram response: {}'.format(j.get('description', '')))
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
headers = Headers({
|
|
134
|
+
b'User-Agent': [b'PGSQLPot']
|
|
135
|
+
})
|
|
136
|
+
params = 'chat_id={}&parse_mode=HTML&text={}'.format(self.chat_id, quote_plus(message))
|
|
137
|
+
url = 'https://api.telegram.org/bot{}/sendMessage?{}'.format(self.bot_token, params)
|
|
138
|
+
d = self.agent.request(b'GET', encode(url), headers, None)
|
|
139
|
+
d.addCallback(cbResponse)
|
|
140
|
+
d.addErrback(cbError)
|
|
141
|
+
return d
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import absolute_import
|
|
3
|
+
|
|
4
|
+
from os import linesep
|
|
5
|
+
from os.path import basename, dirname
|
|
6
|
+
from time import gmtime, strftime
|
|
7
|
+
|
|
8
|
+
from core import output
|
|
9
|
+
from core.config import CONFIG
|
|
10
|
+
from core.tools import mkdir
|
|
11
|
+
from core.logfile import HoneypotDailyLogFile
|
|
12
|
+
|
|
13
|
+
class Output(output.Output):
|
|
14
|
+
|
|
15
|
+
def start(self):
|
|
16
|
+
fn = CONFIG.get('output_textlog', 'logfile')
|
|
17
|
+
dirs = dirname(fn)
|
|
18
|
+
base = basename(fn)
|
|
19
|
+
mkdir(dirs)
|
|
20
|
+
self.outfile = HoneypotDailyLogFile(base, dirs, defaultMode=0o664)
|
|
21
|
+
|
|
22
|
+
def stop(self):
|
|
23
|
+
self.outfile.flush()
|
|
24
|
+
|
|
25
|
+
def write(self, event):
|
|
26
|
+
operation = event['operation'].lower()
|
|
27
|
+
message = '[{} UTC] [PGSQLPot on {} ({})]: {}'.format(
|
|
28
|
+
strftime('%Y-%m-%d %H:%M:%S', gmtime(event['unixtime'])),
|
|
29
|
+
event['sensor'], event['session'], operation.capitalize()
|
|
30
|
+
)
|
|
31
|
+
if operation == 'unknown':
|
|
32
|
+
message += ' operation: "{}"'.format(event['username'])
|
|
33
|
+
elif operation == 'command':
|
|
34
|
+
message += ' "{}'.format(event['command'])
|
|
35
|
+
if event['args']:
|
|
36
|
+
message += ' {}'.format(event['args'])
|
|
37
|
+
message += '"'
|
|
38
|
+
message += ' from {}:{}'.format(event['src_ip'], event['dst_port'])
|
|
39
|
+
if operation == 'login':
|
|
40
|
+
message += ', username: "{}", password: "{}"'.format(
|
|
41
|
+
event['username'], event['password']
|
|
42
|
+
)
|
|
43
|
+
message += '.' + linesep
|
|
44
|
+
|
|
45
|
+
self.outfile.write(message)
|
|
46
|
+
self.outfile.flush()
|
output_plugins/xmpp.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
XMPP output plugin
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import absolute_import
|
|
9
|
+
|
|
10
|
+
from sys import version_info
|
|
11
|
+
from threading import Event, Thread
|
|
12
|
+
from time import gmtime, strftime
|
|
13
|
+
|
|
14
|
+
from core import output
|
|
15
|
+
from core.config import CONFIG
|
|
16
|
+
|
|
17
|
+
import xmpp.dispatcher
|
|
18
|
+
import xmpp.simplexml
|
|
19
|
+
import xmpp.transports
|
|
20
|
+
from xmpp import Client
|
|
21
|
+
from xmpp.protocol import JID, Message
|
|
22
|
+
|
|
23
|
+
from twisted.python.log import msg
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Python 2.7 compatibility patches for xmpppy.
|
|
28
|
+
#
|
|
29
|
+
# xmpppy 0.7.3 has two functions with Python 3-only syntax that crash
|
|
30
|
+
# on Python 2.7:
|
|
31
|
+
#
|
|
32
|
+
# 1. simplexml.ustr(): calls str(r, ENCODING) which is Python 3 bytes-to-str
|
|
33
|
+
# syntax. On Python 2.7 the equivalent is unicode(r).
|
|
34
|
+
#
|
|
35
|
+
# 2. dispatcher.Dispatcher.Process(): calls .with_traceback() unconditionally
|
|
36
|
+
# on exception objects, but that method only exists on Python 3.
|
|
37
|
+
#
|
|
38
|
+
# Both are patched below before any xmpppy code runs.
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
if version_info[0] < 3:
|
|
41
|
+
# Patch 1: simplexml.ustr
|
|
42
|
+
def _ustr(what):
|
|
43
|
+
if isinstance(what, str):
|
|
44
|
+
return what
|
|
45
|
+
try:
|
|
46
|
+
r = what.__str__()
|
|
47
|
+
except AttributeError:
|
|
48
|
+
r = str(what)
|
|
49
|
+
if not isinstance(r, str):
|
|
50
|
+
return r
|
|
51
|
+
return r
|
|
52
|
+
|
|
53
|
+
xmpp.simplexml.ustr = _ustr
|
|
54
|
+
xmpp.transports.ustr = _ustr
|
|
55
|
+
# xmpp.protocol also does 'from .simplexml import ustr' — patch it too
|
|
56
|
+
import xmpp.protocol as _xmpp_protocol
|
|
57
|
+
if hasattr(_xmpp_protocol, 'ustr'):
|
|
58
|
+
_xmpp_protocol.ustr = _ustr
|
|
59
|
+
|
|
60
|
+
# Patch 2: dispatcher.Dispatcher - must be named 'Dispatcher' so that
|
|
61
|
+
# PlugIn() registers it under the right key in owner.__dict__, and must
|
|
62
|
+
# define plugin/plugout explicitly so PlugIn()'s __class__.__dict__ check
|
|
63
|
+
# finds them (inherited methods are not in __class__.__dict__ on Py2
|
|
64
|
+
# old-style classes).
|
|
65
|
+
_OriginalDispatcher = xmpp.dispatcher.Dispatcher
|
|
66
|
+
|
|
67
|
+
class Dispatcher(_OriginalDispatcher):
|
|
68
|
+
def plugin(self, owner):
|
|
69
|
+
_OriginalDispatcher.plugin(self, owner)
|
|
70
|
+
|
|
71
|
+
def plugout(self):
|
|
72
|
+
_OriginalDispatcher.plugout(self)
|
|
73
|
+
|
|
74
|
+
def Process(self, timeout=0):
|
|
75
|
+
for handler in self._cycleHandlers:
|
|
76
|
+
handler(self)
|
|
77
|
+
if len(self._pendingExceptions) > 0:
|
|
78
|
+
_pendingException = self._pendingExceptions.pop()
|
|
79
|
+
ex = _pendingException[0](_pendingException[1])
|
|
80
|
+
if hasattr(ex, 'with_traceback'):
|
|
81
|
+
ex = ex.with_traceback(_pendingException[2])
|
|
82
|
+
raise ex
|
|
83
|
+
if self._owner.Connection.pending_data(timeout):
|
|
84
|
+
try:
|
|
85
|
+
data = self._owner.Connection.receive()
|
|
86
|
+
except IOError:
|
|
87
|
+
return
|
|
88
|
+
self.Stream.Parse(data)
|
|
89
|
+
if len(self._pendingExceptions) > 0:
|
|
90
|
+
_pendingException = self._pendingExceptions.pop()
|
|
91
|
+
ex = _pendingException[0](_pendingException[1])
|
|
92
|
+
if hasattr(ex, 'with_traceback'):
|
|
93
|
+
ex = ex.with_traceback(_pendingException[2])
|
|
94
|
+
raise ex
|
|
95
|
+
if data:
|
|
96
|
+
return len(data)
|
|
97
|
+
return '0'
|
|
98
|
+
|
|
99
|
+
xmpp.dispatcher.Dispatcher = Dispatcher
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Output(output.Output):
|
|
103
|
+
"""
|
|
104
|
+
XMPP output plugin.
|
|
105
|
+
Sends honeypot events as plain-text messages to a configured JID.
|
|
106
|
+
|
|
107
|
+
xmpppy's Process(1) blocks for up to one second per call, so it runs
|
|
108
|
+
in a daemon thread rather than in Twisted's reactor thread. write()
|
|
109
|
+
calls send() directly from the reactor thread; send() only writes to
|
|
110
|
+
the socket while Process() only reads, so there is no write-write race.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def start(self):
|
|
114
|
+
jabberid = CONFIG.get('output_xmpp', 'user')
|
|
115
|
+
password = CONFIG.get('output_xmpp', 'password', raw=True)
|
|
116
|
+
self.receiver = CONFIG.get('output_xmpp', 'muc')
|
|
117
|
+
self._stop_event = Event()
|
|
118
|
+
self._thread = None
|
|
119
|
+
|
|
120
|
+
jid = JID(jabberid)
|
|
121
|
+
self.connection = Client(server=jid.getDomain(), debug=[])
|
|
122
|
+
|
|
123
|
+
result = self.connection.connect()
|
|
124
|
+
if not result:
|
|
125
|
+
msg('output_xmpp: unable to connect to {}'.format(jid.getDomain()))
|
|
126
|
+
self.connection = None
|
|
127
|
+
return
|
|
128
|
+
msg('output_xmpp: connected ({})'.format(result))
|
|
129
|
+
|
|
130
|
+
if not self.connection.auth(user=jid.getNode(), password=password,
|
|
131
|
+
resource=jid.getResource()):
|
|
132
|
+
msg('output_xmpp: authentication failed for {}'.format(jabberid))
|
|
133
|
+
self.connection.disconnect()
|
|
134
|
+
self.connection = None
|
|
135
|
+
return
|
|
136
|
+
msg('output_xmpp: authenticated as {}'.format(jabberid))
|
|
137
|
+
|
|
138
|
+
self._thread = Thread(target=self._process_loop)
|
|
139
|
+
self._thread.daemon = True
|
|
140
|
+
self._thread.start()
|
|
141
|
+
|
|
142
|
+
def _process_loop(self):
|
|
143
|
+
while not self._stop_event.is_set():
|
|
144
|
+
try:
|
|
145
|
+
conn = self.connection
|
|
146
|
+
if conn is None:
|
|
147
|
+
break
|
|
148
|
+
if not conn.Process(1):
|
|
149
|
+
msg('output_xmpp: connection lost')
|
|
150
|
+
self.connection = None
|
|
151
|
+
break
|
|
152
|
+
except Exception as e:
|
|
153
|
+
msg('output_xmpp: error in Process loop: {}'.format(e))
|
|
154
|
+
self.connection = None
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
def stop(self):
|
|
158
|
+
self._stop_event.set()
|
|
159
|
+
if self._thread is not None:
|
|
160
|
+
self._thread.join(timeout=2)
|
|
161
|
+
if self.connection is not None:
|
|
162
|
+
self.connection.disconnect()
|
|
163
|
+
self.connection = None
|
|
164
|
+
|
|
165
|
+
def write(self, event):
|
|
166
|
+
if self.connection is None:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
operation = event['operation'].lower()
|
|
170
|
+
|
|
171
|
+
message = '[{} UTC] [PGSQLPot on {} ({})]: {}'.format(
|
|
172
|
+
strftime('%Y-%m-%d %H:%M:%S', gmtime(event['unixtime'])),
|
|
173
|
+
event['sensor'], event['session'], operation.capitalize()
|
|
174
|
+
)
|
|
175
|
+
if operation == 'unknown':
|
|
176
|
+
message += ' operation: "{}"'.format(event['username'])
|
|
177
|
+
elif operation == 'command':
|
|
178
|
+
message += ' "{}'.format(event['command'])
|
|
179
|
+
if event['args']:
|
|
180
|
+
message += ' {}'.format(event['args'])
|
|
181
|
+
message += '"'
|
|
182
|
+
message += ' from {}:{}'.format(event['src_ip'], event['dst_port'])
|
|
183
|
+
if operation == 'login':
|
|
184
|
+
message += ', username: "{}", password: "{}"'.format(
|
|
185
|
+
event['username'], event['password']
|
|
186
|
+
)
|
|
187
|
+
message += '.'
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
self.connection.send(Message(to=self.receiver, body=message))
|
|
191
|
+
except Exception as e:
|
|
192
|
+
msg('output_xmpp: send failed: {}'.format(e))
|
|
193
|
+
self.connection = None
|
pgsqlpot/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# pgsqlpot: the installable Python package for the pgsqlpot honeypot.
|
|
2
|
+
#
|
|
3
|
+
# After `pip install pgsqlpot`, use the `pgsqlpot` command:
|
|
4
|
+
#
|
|
5
|
+
# pgsqlpot init -- scaffold a working directory
|
|
6
|
+
# pgsqlpot run -- start the honeypot in the foreground
|
|
7
|
+
# pgsqlpot start -- start the honeypot in the background
|
|
8
|
+
# pgsqlpot stop -- stop the background honeypot
|
|
9
|
+
# pgsqlpot restart -- restart the honeypot (stop and start the honeypot in
|
|
10
|
+
# the background)
|
|
11
|
+
# pgsqlpot status -- show running status
|
|
12
|
+
#
|
|
13
|
+
# NOTE: pgsqlpot/honeypot.py is generated at build time by the build_py
|
|
14
|
+
# hook in setup.py - it is a copy of the top-level honeypot.py bundled so
|
|
15
|
+
# that the `pgsqlpot run/start` entry point can locate and run it.
|
|
16
|
+
# It is listed in .gitignore and should not be committed.
|
|
17
|
+
#
|
|
18
|
+
# Contents:
|
|
19
|
+
# cli.py the `pgsqlpot` console entry point (init/run/start/stop/status)
|
|
20
|
+
# honeypot.py the main honeypot script (copied from repo root at build time)
|
|
21
|
+
# data/ bundled read-only assets copied to the working directory
|
|
22
|
+
# by `pgsqlpot init`:
|
|
23
|
+
# docs/ documentation and SQL schema files
|
|
24
|
+
# etc/ default configuration templates
|
|
25
|
+
# test/ test.py for verifying a running honeypot
|