-
Notifications
You must be signed in to change notification settings - Fork 46
/
auto_server.py
executable file
·285 lines (245 loc) · 11.8 KB
/
auto_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#!/usr/bin/python
#
# Auto-OSSEC Server
#
# This is the server piece to the client/server pair (ossec_client.py). Auto-OSSEC will create a protocol
# and allow automatic deployment of OSSEC keys through an enterprise. One of the biggest challenges with
# OSSEC is the key management pieces which auto-ossec tries to solve. When run, this will listen for comms
# with auto_ossec.py which is the OSSEC client and pass a key request to the client through an AES
# encrypted tunnel. Once the exchange completes, auto_ossec will integrate the key and rewrite the conf
# file for you to incorporate the server IP address. View the README.md for usage and how to effecitvely
# use auto-ossec. This also works with AlienVault pairing.
#
# Written by: Dave Kennedy and the Binary Defense Systems (BDS) Team
# Twitter: @HackingDave, @Binary_Defense
# Website: https://www.binarydefense.com
#
# Recommended: Place this python file under supervisor to ensure health, stability, and service starts.
#
# Usage: python auto_sever.py - This will spawn a port listening and wait for connections from auto_ossec.py
#
# Will listen on port 9654 for an incoming challege
#
# Python Crypto and Python Pexpect is required - apt-get install python-crypto python-pexpect
#
# needed for python2/3 compatibility
try: import SocketServer as socketserver
except ImportError: import socketserver
from threading import Thread
import subprocess
import sys
import traceback
import base64
import time
import socket
import os
# python2/3 compatibility
try: import _thread as thread
except ImportError: import thread
# check python crypto library
try:
from Crypto.Cipher import AES
except ImportError:
print("[!] ERROR: pycryptodome not installed. Run 'python3 -m easy_install pycrypto' to fix.")
sys.exit()
# check pexpect library
try:
import pexpect
except ImportError:
print("[!] ERROR: pexpect not installed. Run 'python3 -m easy_install pexpect' to fix.")
sys.exit()
# global lock to restart ossec service
global counter
counter = 0
# global lock for queue
global queue_lock
queue_lock = 0
# main service handler for auto_server
class service(socketserver.BaseRequestHandler):
def handle(self):
# parse OSSEC hids client certificate
def parse_client(hostname, ipaddr):
child = pexpect.spawn("/var/ossec/bin/manage_agents")
child.timeout=300
child.expect("Choose your action")
child.sendline("a")
child.expect("for the new agent")
child.sendline(hostname)
i = child.expect(['IP Address of the new agent', 'already present'])
# if we haven't already added the hostname
if i == 0:
child.sendline(ipaddr)
child.expect("for the new agent")
child.sendline("")
for line in child:
try: line = str(line, 'UTF-8')
except TypeError: line = str(line) # python2 compatibility
# pull id
if "[" in line:
id = line.replace("[", "").replace("]", "").replace(":", "").rstrip()
break
child.expect("Confirm adding it?")
child.sendline("y")
child.sendline("q")
child.close()
child = pexpect.spawn("/var/ossec/bin/manage_agents -e %s" % (id))
child.timeout=300
for line in child: key = line.rstrip() # actual key export
# when no agents are there and one is removed - the agent wont be added properly right away - need to go through the addition again - appears to be an ossec manage bug - going through everything again appears to solve this
time.sleep(0.5)
if "Invalid ID" in str(key): return 0
return key
# if we have a duplicate hostname
else:
child.close()
child = pexpect.spawn("/var/ossec/bin/manage_agents -l")
child.timeout=300
for line in child:
try: line = str(line, 'UTF-8').rstrip()
except TypeError: line = str(line).rstrip() # python 2 and 3 compatibility
if hostname in line:
remove_id = line.split(",")[0].replace("ID: ", "").replace(" ", "").rstrip()
break
child.close()
time.sleep(0.5)
child = pexpect.spawn("/var/ossec/bin/manage_agents -r %s" % (remove_id))
child.timeout=300
child.expect("manage_agents: Exiting.")
time.sleep(2)
child.close()
time.sleep(1)
return 0
def decryptaes(cipher, data, padding):
result = str(cipher.decrypt(base64.b64decode(data)), 'UTF-8').rstrip(padding)
return result
def decryptaes_py2(cipher, data, padding):
result = cipher.decrypt(base64.b64decode(data)).rstrip(padding)
return result
def encryptaes(cipher, data, padding, blocksize):
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (blocksize - len(s) % blocksize) * padding
try: data1 = str(data, 'UTF-8') #print('d1', data1)
except TypeError: data1 = str(data)
data2 = pad(data1) #; print('d2', data2)
data3 = cipher.encrypt(data2) #; print('d3', data3, type(data3))
result = base64.b64encode(data3)
return result
# main AES encrypt and decrypt function with 32 block size padding
def aescall(secret, data, format):
# padding and block size
PADDING = '{'
BLOCK_SIZE = 32
# random value here to randomize builds
a = 50 * 5
# generate the cipher
cipher = AES.new(secret)
if format == "encrypt":
aes = encryptaes(cipher, data, PADDING, BLOCK_SIZE)
return aes
if format == "decrypt":
try: aes = decryptaes(cipher, data, PADDING)
except TypeError: aes = decryptaes_py2(cipher, data, PADDING)
return str(aes)
# recommend changing this - if you do, change auto_ossec.py as well - -
# would recommend this is the default published to git
secret = "(3j+-sa!333hNA2u3h@*!~h~2&^lk<!B"
print("Client connected with ", self.client_address)
try:
data = self.request.recv(1024)
if data != "":
try:
data = aescall(secret, data, "decrypt")
# if this section clears -we know that it is a legit
# request, has been decrypted and we're ready to rock
if "BDSOSSEC" in data:
# if we are using star IP addresses
if "BDSOSSEC*" in data: star = 1
else: star = 0
# process to restart OSSEC if needed every 10 minutes -
# if lock variable is 1 is present then it will trigger a
# restart of OSSEC server
global counter
counter = 1
# strip identifier
data = data.replace("BDSOSSEC*", "").replace("BDSOSSEC", "")
hostname = data
# pull the true IP, not the NATed one if they are using VMWare
if star == 0: ipaddr = self.client_address[0]
else: ipaddr = "0.0.0.0/0"
# this will provision the key
def provision_key(hostname, ipaddr):
ossec_key = parse_client(hostname, ipaddr)
if ossec_key == 0:
ossec_key = parse_client(hostname, ipaddr)
# run through again for trouble ones - ossec bug looks like - but this is a decent workaround
if ossec_key == 0:
ossec_key = parse_client(hostname, ipaddr)
print("[*] Provisioned new key for hostname: %s with IP of: %s" % (hostname, ipaddr))
try: ossec_key = ossec_key.decode('UTF-8')
except: ossec_key = str(ossec_key) # python2 compatibility
ossec_key_crypt = aescall(secret, ossec_key, "encrypt")
try: ossec_key_crypt = str(ossec_key_crypt, 'UTF-8')
except TypeError: ossec_key_crypt = str(ossec_key_crypt)
print("[*] Sending new key to %s: " % (hostname) + str(ossec_key))
# if client disconnected dont crash everything
try: self.request.send(ossec_key_crypt.encode('UTF-8'))
except: pass
time.sleep(1)
# here if the hostname was already used, we need to
# remove it and call it again
global queue_lock
# if our queue lock is 0
if queue_lock == 0:
# locking up the queue to process
queue_lock = 1
provision_key(hostname, ipaddr)
time.sleep(0.2)
queue_lock = 0
# if we aren't ready to provision wait
else:
# we need to wait until its finished
while 1:
# sleep 5 seconds and wait for lock
time.sleep(5)
if queue_lock == 0:
print("Queue is now free, proceeding with current agent additions..")
queue_lock = 1
provision_key(hostname, ipaddr)
time.sleep(0.2)
queue_lock = 0
break
except Exception as e:
print(e)
traceback.print_exc(file=sys.stdout)
pass
except Exception as e:
print(e)
pass
print("Pairing complete. Terminating connection to client.")
self.request.close()
# this waits 5 minutes to check if new ossec agents have been deployed, if
# so it restarts the server
def ossec_monitor():
while 1:
time.sleep(300)
global counter
if counter == 1:
# if we dont have any new agents being added at the time
if queue_lock == 0:
print("[*] New OSSEC agent added - triggering restart of service to add..")
subprocess.Popen("service ossec restart", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).wait()
counter = 0
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): pass
print("[*] The auto enrollment OSSEC Server is now listening on 9654")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# set is so that when we cancel out we can reuse port
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind to all interfaces on port 10900
ThreadedTCPServer.allow_reuse_address = True
t = ThreadedTCPServer(('', 9654), service)
# start the server and listen forever
try:
# start a threaded counter
thread.start_new_thread(ossec_monitor, ())
t.serve_forever()
except KeyboardInterrupt: print("[*] Exiting the automatic enrollment OSSEC daemon")