diff --git a/docker/ciscoasa/Dockerfile b/docker/ciscoasa/Dockerfile
index 21d455dd..b03136fb 100644
--- a/docker/ciscoasa/Dockerfile
+++ b/docker/ciscoasa/Dockerfile
@@ -1,5 +1,8 @@
FROM alpine
+# Include dist
+ADD dist/ /root/dist/
+
# Setup env and apt
RUN apk -U upgrade && \
apk add bash \
@@ -19,7 +22,8 @@ RUN apk -U upgrade && \
cd /opt/ && \
git clone https://github.com/cymmetria/ciscoasa_honeypot && \
cd ciscoasa_honeypot && \
- pip3 install -r requirements.txt && \
+ pip3 install --no-cache-dir -r requirements.txt && \
+ cp /root/dist/asa_server.py /opt/ciscoasa_honeypot && \
chown -R ciscoasa:ciscoasa /opt/ciscoasa_honeypot && \
# Clean up
@@ -33,6 +37,7 @@ RUN apk -U upgrade && \
python3
# Start elasticsearch-head
-WORKDIR /opt/ciscoasa_honeypot
+WORKDIR /tmp/ciscoasa/
USER ciscoasa:ciscoasa
-CMD exec python3 asa_server.py --enable_ssl --verbose >> /var/log/ciscoasa/ciscoasa.log 2>&1
+CMD cp -R /opt/ciscoasa_honeypot/* /tmp/ciscoasa && exec python3 asa_server.py --enable_ssl --verbose >> /var/log/ciscoasa/ciscoasa.log 2>&1
+#CMD cp -R /opt/ciscoasa_honeypot/* /tmp/ciscoasa && exec python3 asa_server.py --enable_ssl >> /var/log/ciscoasa/ciscoasa.log 2>&1
diff --git a/docker/ciscoasa/dist/asa_server.py b/docker/ciscoasa/dist/asa_server.py
new file mode 100644
index 00000000..d02803af
--- /dev/null
+++ b/docker/ciscoasa/dist/asa_server.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import os
+import datetime
+import socket
+import logging
+logging.basicConfig(format='%(message)s')
+import threading
+from io import BytesIO
+from xml.etree import ElementTree
+from http.server import HTTPServer
+from socketserver import ThreadingMixIn
+from http.server import SimpleHTTPRequestHandler
+import ike_server
+
+
+class NonBlockingHTTPServer(ThreadingMixIn, HTTPServer):
+ pass
+
+
+def header_split(h):
+ return [list(map(str.strip, l.split(': ', 1))) for l in h.strip().splitlines()]
+
+
+class WebLogicHandler(SimpleHTTPRequestHandler):
+ logger = None
+
+ protocol_version = "HTTP/1.1"
+
+ EXPLOIT_STRING = b"host-scan-reply"
+ RESPONSE = b"""
+
+9.0(1)
+VPN Server could not parse request.
+"""
+
+ basepath = os.path.dirname(os.path.abspath(__file__))
+
+ alert_function = None
+
+ def setup(self):
+ SimpleHTTPRequestHandler.setup(self)
+ self.request.settimeout(3)
+
+ def send_header(self, keyword, value):
+ if keyword.lower() == 'server':
+ return
+ SimpleHTTPRequestHandler.send_header(self, keyword, value)
+
+ def send_head(self):
+ # send_head will return a file object that do_HEAD/GET will use
+ # do_GET/HEAD are already implemented by SimpleHTTPRequestHandler
+ filename = os.path.basename(self.path.rstrip('/').split('?', 1)[0])
+
+ if self.path == '/':
+ self.send_response(200)
+ for k, v in header_split("""
+ Content-Type: text/html
+ Cache-Control: no-cache
+ Pragma: no-cache
+ Set-Cookie: tg=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ Set-Cookie: webvpn=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ Set-Cookie: webvpnc=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ Set-Cookie: webvpn_portal=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ Set-Cookie: webvpnSharePoint=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ Set-Cookie: webvpnlogin=1; path=/; secure
+ Set-Cookie: sdesktop=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ """):
+ self.send_header(k, v)
+ self.end_headers()
+ return BytesIO(b'\n')
+ elif filename == 'asa': # don't allow dir listing
+ return self.send_file('wrong_url.html', 403)
+ else:
+ return self.send_file(filename)
+
+ def redirect(self, loc):
+ self.send_response(302)
+ for k, v in header_split("""
+ Content-Type: text/html
+ Content-Length: 0
+ Cache-Control: no-cache
+ Pragma: no-cache
+ Location: %s
+ Set-Cookie: tg=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ """ % (loc,)):
+ self.send_header(k, v)
+ self.end_headers()
+
+ def do_GET(self):
+ if self.path == '/+CSCOE+/logon.html':
+ self.redirect('/+CSCOE+/logon.html?fcadbadd=1')
+ return
+ elif self.path.startswith('/+CSCOE+/logon.html?') and 'reason=1' in self.path:
+ self.wfile.write(self.send_file('logon_failure').getvalue())
+ return
+ SimpleHTTPRequestHandler.do_GET(self)
+
+ def do_POST(self):
+ data_len = int(self.headers.get('Content-length', 0))
+ data = self.rfile.read(data_len) if data_len else b''
+ body = self.RESPONSE
+ if self.EXPLOIT_STRING in data:
+ xml = ElementTree.fromstring(data)
+ payloads = []
+ for x in xml.iter('host-scan-reply'):
+ payloads.append(x.text)
+
+ self.alert_function(self.client_address[0], self.client_address[1], payloads)
+
+ elif self.path == '/':
+ self.redirect('/+webvpn+/index.html')
+ return
+ elif self.path == '/+CSCOE+/logon.html':
+ self.redirect('/+CSCOE+/logon.html?fcadbadd=1')
+ return
+ elif self.path.split('?', 1)[0] == '/+webvpn+/index.html':
+ with open(os.path.join(self.basepath, 'asa', "logon_redir.html"), 'rb') as fh:
+ body = fh.read()
+
+ self.send_response(200)
+ self.send_header('Content-Length', int(len(body)))
+ self.send_header('Content-Type', 'text/html; charset=UTF-8')
+ self.end_headers()
+ self.wfile.write(body)
+ return
+
+ def send_file(self, filename, status_code=200, headers=[]):
+ try:
+ with open(os.path.join(self.basepath, 'asa', filename), 'rb') as fh:
+ body = fh.read()
+ self.send_response(status_code)
+ for k, v in headers:
+ self.send_header(k, v)
+ if status_code == 200:
+ for k, v in header_split("""
+ Cache-Control: max-age=0
+ Set-Cookie: webvpn=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ Set-Cookie: webvpnc=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure
+ Set-Cookie: webvpnlogin=1; secure
+ X-Transcend-Version: 1
+ """):
+ self.send_header(k, v)
+ self.send_header('Content-Length', int(len(body)))
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ return BytesIO(body)
+ except IOError:
+ return self.send_file('wrong_url.html', 404)
+
+ def log_message(self, format, *args):
+ self.logger.debug("{'timestamp': '%s', 'src_ip': '%s', 'payload_printable': '%s'}" %
+ (datetime.datetime.utcnow().isoformat(),
+ self.client_address[0],
+ format % args))
+
+ def handle_one_request(self):
+ """Handle a single HTTP request.
+ Overriden to not send 501 errors
+ """
+ self.close_connection = True
+ try:
+ self.raw_requestline = self.rfile.readline(65537)
+ if len(self.raw_requestline) > 65536:
+ self.requestline = ''
+ self.request_version = ''
+ self.command = ''
+ self.close_connection = 1
+ return
+ if not self.raw_requestline:
+ self.close_connection = 1
+ return
+ if not self.parse_request():
+ # An error code has been sent, just exit
+ return
+ mname = 'do_' + self.command
+ if not hasattr(self, mname):
+ self.log_request()
+ self.close_connection = True
+ return
+ method = getattr(self, mname)
+ method()
+ self.wfile.flush() # actually send the response if not already done.
+ except socket.timeout as e:
+ # a read or a write timed out. Discard this connection
+ self.log_error("Request timed out: %r", e)
+ self.close_connection = 1
+ return
+
+
+if __name__ == '__main__':
+ import click
+
+ logging.basicConfig(level=logging.INFO)
+ logger = logging.getLogger()
+ logger.info('info')
+
+ @click.command()
+ @click.option('-h', '--host', default='0.0.0.0', help='Host to listen')
+ @click.option('-p', '--port', default=8443, help='Port to listen', type=click.INT)
+ @click.option('-i', '--ike-port', default=5000, help='Port to listen for IKE', type=click.INT)
+ @click.option('-s', '--enable_ssl', default=False, help='Enable SSL', is_flag=True)
+ @click.option('-c', '--cert', default=None, help='Certificate File Path (will generate self signed '
+ 'cert if not supplied)')
+ @click.option('-v', '--verbose', default=False, help='Verbose logging', is_flag=True)
+ def start(host, port, ike_port, enable_ssl, cert, verbose):
+ """
+ A low interaction honeypot for the Cisco ASA component capable of detecting CVE-2018-0101,
+ a DoS and remote code execution vulnerability
+ """
+ def alert(cls, host, port, payloads):
+ logger.critical({
+ 'timestamp': datetime.datetime.utcnow().isoformat(),
+ 'src_ip': host,
+ 'src_port': port,
+ 'payload_printable': payloads,
+ })
+
+ if verbose:
+ logger.setLevel(logging.DEBUG)
+
+ requestHandler = WebLogicHandler
+ requestHandler.alert_function = alert
+ requestHandler.logger = logger
+
+ def log_date_time_string():
+ """Return the current time formatted for logging."""
+ now = datetime.datetime.utcnow().isoformat()
+ return now
+
+ def ike():
+ ike_server.start(host, ike_port, alert, logger)
+ t = threading.Thread(target=ike)
+ t.daemon = True
+ t.start()
+
+ httpd = HTTPServer((host, port), requestHandler)
+ if enable_ssl:
+ import ssl
+ if not cert:
+ import gencert
+ cert = gencert.gencert()
+ httpd.socket = ssl.wrap_socket(httpd.socket, certfile=cert, server_side=True)
+
+ logger.info('Starting server on port {:d}/tcp, use to stop'.format(port))
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+ logger.info('Stopping server.')
+ httpd.server_close()
+
+ start()
diff --git a/docker/ciscoasa/docker-compose.yml b/docker/ciscoasa/docker-compose.yml
index a87845a7..227b1203 100644
--- a/docker/ciscoasa/docker-compose.yml
+++ b/docker/ciscoasa/docker-compose.yml
@@ -13,11 +13,14 @@ services:
container_name: ciscoasa
restart: always
stop_signal: SIGINT
+ tmpfs:
+ - /tmp/ciscoasa:uid=2000,gid=2000
networks:
- ciscoasa_local
ports:
- "5000:5000"
- "8443:8443"
image: "dtagdevsec/ciscoasa:1804"
+ read_only: true
volumes:
- /data/ciscoasa/log:/var/log/ciscoasa
diff --git a/docker/conpot/docker-compose.yml b/docker/conpot/docker-compose.yml
index 3f1b9185..2b68e5b0 100644
--- a/docker/conpot/docker-compose.yml
+++ b/docker/conpot/docker-compose.yml
@@ -23,7 +23,7 @@ services:
- CONPOT_TEMPLATE=default
- CONPOT_TMP=/tmp/conpot
tmpfs:
- - /tmp/conpot:exec
+ - /tmp/conpot:uid=2000,gid=2000
networks:
- conpot_local_default
ports:
@@ -52,7 +52,7 @@ services:
- CONPOT_TEMPLATE=IEC104
- CONPOT_TMP=/tmp/conpot
tmpfs:
- - /tmp/conpot:exec
+ - /tmp/conpot:uid=2000,gid=2000
networks:
- conpot_local_IEC104
ports:
@@ -76,7 +76,7 @@ services:
- CONPOT_TEMPLATE=guardian_ast
- CONPOT_TMP=/tmp/conpot
tmpfs:
- - /tmp/conpot:exec
+ - /tmp/conpot:uid=2000,gid=2000
networks:
- conpot_local_guardian_ast
ports:
@@ -99,7 +99,7 @@ services:
- CONPOT_TEMPLATE=ipmi
- CONPOT_TMP=/tmp/conpot
tmpfs:
- - /tmp/conpot:exec
+ - /tmp/conpot:uid=2000,gid=2000
networks:
- conpot_local_ipmi
ports:
@@ -122,7 +122,7 @@ services:
- CONPOT_TEMPLATE=kamstrup_382
- CONPOT_TMP=/tmp/conpot
tmpfs:
- - /tmp/conpot:exec
+ - /tmp/conpot:uid=2000,gid=2000
networks:
- conpot_local_kamstrup_382
ports:
diff --git a/docker/elk/logstash/dist/logstash.conf b/docker/elk/logstash/dist/logstash.conf
index 0231e426..e2488c14 100644
--- a/docker/elk/logstash/dist/logstash.conf
+++ b/docker/elk/logstash/dist/logstash.conf
@@ -15,6 +15,13 @@ input {
type => "P0f"
}
+# Ciscoasa
+ file {
+ path => ["/data/ciscoasa/log/ciscoasa.log"]
+ codec => plain
+ type => "Ciscoasa"
+ }
+
# Conpot
file {
path => ["/data/conpot/log/*.json"]
@@ -140,6 +147,19 @@ filter {
}
}
+# Ciscoasa
+ if [type] == "Ciscoasa" {
+ kv {
+ remove_char_key => " '{}"
+ remove_char_value => "'{}"
+ value_split => ":"
+ field_split => ","
+ }
+ date {
+ match => [ "timestamp", "ISO8601" ]
+ }
+ }
+
# Conpot
if [type] == "ConPot" {
date {
@@ -410,7 +430,7 @@ if "_grokparsefailure" in [tags] { drop {} }
}
# Add T-Pot hostname and external IP
- if [type] == "ConPot" or [type] == "Cowrie" or [type] == "Dionaea" or [type] == "ElasticPot" or [type] == "eMobility" or [type] == "Glastopf" or [type] == "Honeytrap" or [type] == "Heralding" or [type] == "Mailoney" or [type] == "Rdpy" or [type] == "Suricata" or [type] == "Vnclowpot" {
+ if [type] == "Ciscoasa" or [type] == "ConPot" or [type] == "Cowrie" or [type] == "Dionaea" or [type] == "ElasticPot" or [type] == "eMobility" or [type] == "Glastopf" or [type] == "Honeytrap" or [type] == "Heralding" or [type] == "Mailoney" or [type] == "Rdpy" or [type] == "Suricata" or [type] == "Vnclowpot" {
mutate {
add_field => {
"t-pot_ip_ext" => "${MY_EXTIP}"