Przeglądaj źródła

feature/tarpit (#56)

add file header

bump version

add ssh tarpit, add fail2ban ssh config, harden ssh

fix bug, supervisor conf is a file not a template

initial http tarpit

Co-authored-by: Jonathan Cobb <jonathan@kyuss.org>
Reviewed-on: #56
tags/v1.2.0
jonathan 4 lat temu
rodzic
commit
1c1d5c380f
14 zmienionych plików z 309 dodań i 11 usunięć
  1. +1
    -1
      bubble-server/src/main/resources/META-INF/bubble/bubble.properties
  2. +2
    -2
      bubble-server/src/main/resources/ansible/roles/mitmproxy/tasks/main.yml
  3. +1
    -0
      bubble-server/src/main/resources/packer/node-roles.txt
  4. +9
    -0
      bubble-server/src/main/resources/packer/roles/firewall/files/bubble_sshd.conf
  5. +5
    -0
      bubble-server/src/main/resources/packer/roles/firewall/files/jail.local
  6. +21
    -2
      bubble-server/src/main/resources/packer/roles/firewall/tasks/main.yml
  7. +5
    -2
      bubble-server/src/main/resources/packer/roles/firewall/tasks/rules.yml
  8. +19
    -0
      bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py
  9. +4
    -4
      bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_request.py
  10. +89
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_http_tarpit.py
  11. +87
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_ssh_tarpit.py
  12. +6
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_http_tarpit.conf
  13. +6
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_ssh_tarpit.conf
  14. +54
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/tasks/main.yml

+ 1
- 1
bubble-server/src/main/resources/META-INF/bubble/bubble.properties Wyświetl plik

@@ -1 +1 @@
bubble.version=Adventure 1.1.4
bubble.version=Adventure 1.2.0

+ 2
- 2
bubble-server/src/main/resources/ansible/roles/mitmproxy/tasks/main.yml Wyświetl plik

@@ -18,7 +18,7 @@
- name: Ensure mitmproxy user owns all mitmproxy files
shell: chown -R mitmproxy /home/mitmproxy/mitmproxy

- name: Install mitmproxy1 supervisor conf file
- name: Install mitm8888 supervisor conf file
template:
src: supervisor_mitmproxy.conf.j2
dest: /etc/supervisor/conf.d/mitm8888.conf
@@ -28,7 +28,7 @@
vars:
port: 8888

- name: Install mitmproxy2 supervisor conf file
- name: Install mitm9999 supervisor conf file
template:
src: supervisor_mitmproxy.conf.j2
dest: /etc/supervisor/conf.d/mitm9999.conf


+ 1
- 0
bubble-server/src/main/resources/packer/node-roles.txt Wyświetl plik

@@ -1,5 +1,6 @@
common
firewall
tarpit
nginx
algo
mitmproxy


+ 9
- 0
bubble-server/src/main/resources/packer/roles/firewall/files/bubble_sshd.conf Wyświetl plik

@@ -0,0 +1,9 @@
Port 1202
LoginGraceTime 10
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
X11Forwarding no
PermitUserEnvironment no

+ 5
- 0
bubble-server/src/main/resources/packer/roles/firewall/files/jail.local Wyświetl plik

@@ -0,0 +1,5 @@
[sshd]
mode = aggressive
port = 1202
logpath = %(sshd_log)s
backend = %(sshd_backend)s

+ 21
- 2
bubble-server/src/main/resources/packer/roles/firewall/tasks/main.yml Wyświetl plik

@@ -90,15 +90,34 @@
dest: /usr/local/bin/bubble_peer_manager.py
owner: root
group: root
mode: 0555
mode: 0550
when: fw_enable_admin

- name: Install supervisor conf file for port manager
- name: Install supervisor conf file for peer manager
copy:
src: supervisor_bubble_peer_manager.conf
dest: /etc/supervisor/conf.d/bubble_peer_manager.conf
owner: root
group: root
mode: 0550
when: fw_enable_admin

- name: Install SSH hardening settings
copy:
src: bubble_sshd.conf
dest: /etc/ssh/ssh_config.d/bubble_sshd.conf
owner: root
group: root
mode: 0400

- name: Install SSH fail2ban settings
copy:
src: jail.local
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: 0400

- include: rules.yml

- supervisorctl:


+ 5
- 2
bubble-server/src/main/resources/packer/roles/firewall/tasks/rules.yml Wyświetl plik

@@ -17,15 +17,18 @@
comment: Allow related and established connections
become: yes

- name: Allow SSH
- name: Allow SSH on ports 22 and 1202
iptables:
chain: INPUT
protocol: tcp
destination_port: 22
destination_port: "{{ item }}"
ctstate: NEW
syn: match
jump: ACCEPT
comment: Accept new SSH connections
with_items:
- 22
- 1202
become: yes
when: fw_enable_ssh



+ 19
- 0
bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py Wyświetl plik

@@ -64,6 +64,7 @@ LOCAL_IPS = []
for local_ip in subprocess.check_output(['hostname', '-I']).split():
LOCAL_IPS.append(local_ip.decode())

TARPIT_PORT = 8080

VPN_IP4_CIDR = IPNetwork(wireguard_network_ipv4)
VPN_IP6_CIDR = IPNetwork(wireguard_network_ipv6)
@@ -469,6 +470,24 @@ def health_check_response(flow):
flow.response.stream = lambda chunks: [b'OK\n']


def tarpit_response(flow, host):
# if bubble_log.isEnabledFor(DEBUG):
# bubble_log.debug('health_check_response: special bubble health check request, responding with OK')
response_headers = nheaders.Headers()
response_headers[HEADER_LOCATION] = 'http://'+host+':'+str(TARPIT_PORT)+'/admin/index.php'
if flow.response is None:
flow.response = http.HTTPResponse(http_version='HTTP/1.1',
status_code=301,
reason='Moved Permanently',
headers=response_headers,
content=b'')
else:
flow.response.headers = nheaders.Headers()
flow.response.headers = response_headers
flow.response.status_code = 301
flow.response.reason = 'Moved Permanently'


def include_request_headers(path):
return '/followAndApplyRegex' in path



+ 4
- 4
bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_request.py Wyświetl plik

@@ -32,7 +32,7 @@ from mitmproxy.net.http import headers as nheaders
from bubble_api import bubble_matchers, bubble_activity_log, \
CTX_BUBBLE_MATCHERS, CTX_BUBBLE_SPECIAL, CTX_BUBBLE_ABORT, CTX_BUBBLE_LOCATION, \
CTX_BUBBLE_PASSTHRU, CTX_BUBBLE_FLEX, CTX_BUBBLE_REQUEST_ID, add_flow_ctx, parse_host_header, \
is_bubble_special_path, is_bubble_health_check, health_check_response, \
is_bubble_special_path, is_bubble_health_check, health_check_response, tarpit_response,\
is_bubble_request, is_sage_request, is_not_from_vpn, is_flex_domain
from bubble_config import bubble_host, bubble_host_alias
from bubble_flex import new_flex_flow
@@ -170,9 +170,9 @@ class Rerouter:
elif is_not_from_vpn(client_addr):
# todo: add to fail2ban
if bubble_log.isEnabledFor(WARNING):
bubble_log.warning('bubble_handle_request: returning 404 for non-VPN client='+client_addr+', url='+log_url+' host='+host)
bubble_activity_log(client_addr, server_addr, 'http_abort_non_vpn', fqdns)
add_flow_ctx(flow, CTX_BUBBLE_ABORT, 404)
bubble_log.warning('bubble_handle_request: sending to tarpit: non-VPN client='+client_addr+', url='+log_url+' host='+host)
bubble_activity_log(client_addr, server_addr, 'http_tarpit_non_vpn', fqdns)
tarpit_response(flow, host)
return None

if is_bubble_special_path(path):


+ 89
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_http_tarpit.py Wyświetl plik

@@ -0,0 +1,89 @@
#!/usr/bin/python3
#
# Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
#
# Adapted from: https://nullprogram.com/blog/2019/03/22/
#
import asyncio
import random
import os
import sys

import logging
from logging import INFO, DEBUG, WARNING, ERROR, CRITICAL

from pathlib import Path

TARPIT_LOG = '/var/log/bubble/http_tarpit.log'
TARPIT_LOG_LEVEL_FILE = '/home/tarpit/http_tarpit_log_level.txt'
TARPIT_LOG_LEVEL_ENV_VAR = 'HTTP_TARPIT_LOG_LEVEL'
DEFAULT_TARPIT_LOG_LEVEL = 'INFO'
TARPIT_LOG_LEVEL = None

TARPIT_PORT_FILE = '/home/tarpit/http_tarpit_port.txt'
TARPIT_PORT_ENV_VAR = 'HTTP_TARPIT_PORT'
DEFAULT_TARPIT_PORT = '8080'

tarpit_log = logging.getLogger(__name__)

try:
TARPIT_LOG_LEVEL = Path(TARPIT_LOG_LEVEL_FILE).read_text().strip()
except IOError:
print('error reading log level from '+TARPIT_LOG_LEVEL_FILE+', checking env var '+TARPIT_LOG_LEVEL_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_LOG_LEVEL = os.getenv(TARPIT_LOG_LEVEL_ENV_VAR, DEFAULT_TARPIT_LOG_LEVEL)

TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, TARPIT_LOG_LEVEL.upper(), None)
if not isinstance(TARPIT_NUMERIC_LOG_LEVEL, int):
print('Invalid log level: ' + TARPIT_LOG_LEVEL + ' - using default '+DEFAULT_TARPIT_LOG_LEVEL, file=sys.stderr, flush=True)
TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, DEFAULT_TARPIT_LOG_LEVEL.upper(), None)

try:
with open(TARPIT_LOG, 'w+') as f:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', filename=TARPIT_LOG, level=TARPIT_NUMERIC_LOG_LEVEL)
except IOError:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', stream=sys.stdout, level=TARPIT_NUMERIC_LOG_LEVEL)

tarpit_log = logging.getLogger(__name__)

if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('tarpit initialized, default log level = '+logging.getLevelName(TARPIT_NUMERIC_LOG_LEVEL))

TARPIT_PORT = 8080
try:
TARPIT_PORT = int(Path(TARPIT_PORT_FILE).read_text().strip())
except IOError:
print('error reading port from '+TARPIT_PORT_FILE+', checking env var '+TARPIT_PORT_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_PORT = int(os.getenv(TARPIT_PORT_ENV_VAR, DEFAULT_TARPIT_PORT))

TRAP_COUNT = 0


async def handler(_reader, writer):
global TRAP_COUNT
TRAP_COUNT = TRAP_COUNT + 1
peer_addr = writer.get_extra_info('socket').getpeername()[0]
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('trapped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
writer.write(b'HTTP/1.1 200 OK\r\n')
try:
while True:
header = random.randint(0, 2**32)
value = random.randint(0, 2**32)
await asyncio.sleep(3 + (header % 4))
writer.write(b'X-WOPR-%x: %x\r\n' % (header, value))
await writer.drain()
except ConnectionResetError:
TRAP_COUNT = TRAP_COUNT - 1
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('dropped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
pass


async def main():
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('starting HTTP tarpit on port '+str(TARPIT_PORT))
server = await asyncio.start_server(handler, '0.0.0.0', TARPIT_PORT)
async with server:
await server.serve_forever()

asyncio.run(main())

+ 87
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_ssh_tarpit.py Wyświetl plik

@@ -0,0 +1,87 @@
#!/usr/bin/python3
#
# Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
#
# Adapted from: https://nullprogram.com/blog/2019/03/22/
#
import asyncio
import random
import os
import sys

import logging
from logging import INFO, DEBUG, WARNING, ERROR, CRITICAL

from pathlib import Path

TARPIT_LOG = '/var/log/bubble/ssh_tarpit.log'
TARPIT_LOG_LEVEL_FILE = '/home/tarpit/ssh_tarpit_log_level.txt'
TARPIT_LOG_LEVEL_ENV_VAR = 'SSH_TARPIT_LOG_LEVEL'
DEFAULT_TARPIT_LOG_LEVEL = 'INFO'
TARPIT_LOG_LEVEL = None

TARPIT_PORT_FILE = '/home/tarpit/ssh_tarpit_port.txt'
TARPIT_PORT_ENV_VAR = 'SSH_TARPIT_PORT'
DEFAULT_TARPIT_PORT = '22'

tarpit_log = logging.getLogger(__name__)

try:
TARPIT_LOG_LEVEL = Path(TARPIT_LOG_LEVEL_FILE).read_text().strip()
except IOError:
print('error reading log level from '+TARPIT_LOG_LEVEL_FILE+', checking env var '+TARPIT_LOG_LEVEL_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_LOG_LEVEL = os.getenv(TARPIT_LOG_LEVEL_ENV_VAR, DEFAULT_TARPIT_LOG_LEVEL)

TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, TARPIT_LOG_LEVEL.upper(), None)
if not isinstance(TARPIT_NUMERIC_LOG_LEVEL, int):
print('Invalid log level: ' + TARPIT_LOG_LEVEL + ' - using default '+DEFAULT_TARPIT_LOG_LEVEL, file=sys.stderr, flush=True)
TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, DEFAULT_TARPIT_LOG_LEVEL.upper(), None)

try:
with open(TARPIT_LOG, 'w+') as f:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', filename=TARPIT_LOG, level=TARPIT_NUMERIC_LOG_LEVEL)
except IOError:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', stream=sys.stdout, level=TARPIT_NUMERIC_LOG_LEVEL)

tarpit_log = logging.getLogger(__name__)

if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('tarpit initialized, default log level = '+logging.getLevelName(TARPIT_NUMERIC_LOG_LEVEL))

TARPIT_PORT = 8080
try:
TARPIT_PORT = int(Path(TARPIT_PORT_FILE).read_text().strip())
except IOError:
print('error reading port from '+TARPIT_PORT_FILE+', checking env var '+TARPIT_PORT_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_PORT = int(os.getenv(TARPIT_PORT_ENV_VAR, DEFAULT_TARPIT_PORT))

TRAP_COUNT = 0


async def handler(_reader, writer):
global TRAP_COUNT
TRAP_COUNT = TRAP_COUNT + 1
peer_addr = writer.get_extra_info('socket').getpeername()[0]
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('trapped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
try:
while True:
val = random.randint(0, 2 ** 32)
await asyncio.sleep(6 + (val % 4))
writer.write(b'%x\r\n' % val)
await writer.drain()
except ConnectionResetError:
TRAP_COUNT = TRAP_COUNT - 1
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('dropped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
pass


async def main():
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('starting SSH tarpit on port '+str(TARPIT_PORT))
server = await asyncio.start_server(handler, '0.0.0.0', TARPIT_PORT)
async with server:
await server.serve_forever()

asyncio.run(main())

+ 6
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_http_tarpit.conf Wyświetl plik

@@ -0,0 +1,6 @@

[program:http_tarpit]
stdout_logfile = /dev/null
stderr_logfile = /dev/null
command=sudo -u tarpit /home/tarpit/bubble_http_tarpit.py
stopsignal=QUIT

+ 6
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_ssh_tarpit.conf Wyświetl plik

@@ -0,0 +1,6 @@

[program:ssh_tarpit]
stdout_logfile = /dev/null
stderr_logfile = /dev/null
command=/home/tarpit/bubble_ssh_tarpit.py
stopsignal=QUIT

+ 54
- 0
bubble-server/src/main/resources/packer/roles/tarpit/tasks/main.yml Wyświetl plik

@@ -0,0 +1,54 @@
#
# Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
#
- name: Create tarpit user
user:
name: tarpit
comment: tarpit user
shell: /bin/false
system: yes
home: /home/tarpit
groups: bubble-log

- name: Copy bubble_ssh_tarpit script
copy:
src: bubble_ssh_tarpit.py
dest: /home/tarpit/bubble_ssh_tarpit.py
owner: tarpit
group: tarpit
mode: 0500

- name: Copy bubble_http_tarpit script
copy:
src: bubble_http_tarpit.py
dest: /home/tarpit/bubble_http_tarpit.py
owner: tarpit
group: tarpit
mode: 0500

- name: Install ssh tarpit supervisor conf file
copy:
src: supervisor_ssh_tarpit.conf
dest: /etc/supervisor/conf.d/ssh_tarpit.conf
owner: root
group: root
mode: 0400

- name: Install http tarpit supervisor conf file
copy:
src: supervisor_http_tarpit.conf
dest: /etc/supervisor/conf.d/http_tarpit.conf
owner: root
group: root
mode: 0400

- name: Allow HTTP tarpit port
iptables:
chain: INPUT
protocol: tcp
destination_port: 8080
ctstate: NEW
syn: match
jump: ACCEPT
comment: Accept new connections on HTTP tarpit port
become: yes

Ładowanie…
Anuluj
Zapisz