mirror of https://github.com/MISP/misp-modules
180 lines
7.4 KiB
Python
180 lines
7.4 KiB
Python
|
# encoding: utf-8
|
||
|
import json
|
||
|
import base64
|
||
|
import codecs
|
||
|
from dateutil.parser import isoparse
|
||
|
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
||
|
try:
|
||
|
import censys.base
|
||
|
import censys.ipv4
|
||
|
import censys.websites
|
||
|
import censys.certificates
|
||
|
except ImportError:
|
||
|
print("Censys module not installed. Try 'pip install censys'")
|
||
|
|
||
|
misperrors = {'error': 'Error'}
|
||
|
moduleconfig = ['api_id', 'api_secret']
|
||
|
mispattributes = {'input': ['ip-src', 'ip-dst', 'domain', 'x509-fingerprint-md5', 'x509-fingerprint-sha1', 'x509-fingerprint-sha256'], 'format': 'misp_standard'}
|
||
|
moduleinfo = {'version': '0.1', 'author': 'Loïc Fortemps',
|
||
|
'description': 'Censys.io expansion module', 'module-type': ['expansion', 'hover']}
|
||
|
|
||
|
|
||
|
def handler(q=False):
|
||
|
if q is False:
|
||
|
return False
|
||
|
request = json.loads(q)
|
||
|
|
||
|
if request.get('config'):
|
||
|
if (request['config'].get('api_id') is None) or (request['config'].get('api_secret') is None):
|
||
|
misperrors['error'] = "Censys API credentials are missing"
|
||
|
return misperrors
|
||
|
else:
|
||
|
misperrors['error'] = "Please provide config options"
|
||
|
return misperrors
|
||
|
|
||
|
api_id = request['config']['api_id']
|
||
|
api_secret = request['config']['api_secret']
|
||
|
|
||
|
if not request.get('attribute'):
|
||
|
return {'error': 'Unsupported input.'}
|
||
|
attribute = request['attribute']
|
||
|
if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
|
||
|
return {'error': 'Unsupported attributes type'}
|
||
|
|
||
|
attribute = MISPAttribute()
|
||
|
attribute.from_dict(**request['attribute'])
|
||
|
|
||
|
if attribute.type == 'ip-dst' or attribute.type == 'ip-src':
|
||
|
conn = censys.ipv4.CensysIPv4(api_id=api_id, api_secret=api_secret)
|
||
|
elif attribute.type == 'domain':
|
||
|
conn = censys.websites.CensysWebsites(api_id=api_id, api_secret=api_secret)
|
||
|
elif 'x509-fingerprint' in attribute.type:
|
||
|
conn = censys.certificates.CensysCertificates(api_id=api_id, api_secret=api_secret)
|
||
|
else:
|
||
|
return False
|
||
|
|
||
|
try:
|
||
|
result = conn.view(attribute.value)
|
||
|
except censys.base.CensysNotFoundException:
|
||
|
misperrors['error'] = "Nothing could be found on Censys"
|
||
|
return misperrors
|
||
|
except Exception:
|
||
|
misperrors['error'] = "Connection issue"
|
||
|
return misperrors
|
||
|
|
||
|
r = {'results': parse_response(result, attribute)}
|
||
|
return r
|
||
|
|
||
|
|
||
|
def parse_response(censys_output, attribute):
|
||
|
misp_event = MISPEvent()
|
||
|
misp_event.add_attribute(**attribute)
|
||
|
# Generic fields (for IP/Websites)
|
||
|
if "autonomous_system" in censys_output:
|
||
|
cen_as = censys_output['autonomous_system']
|
||
|
asn_object = MISPObject('asn')
|
||
|
asn_object.add_attribute('asn', value=cen_as["asn"])
|
||
|
asn_object.add_attribute('description', value=cen_as['name'])
|
||
|
asn_object.add_attribute('subnet-announced', value=cen_as['routed_prefix'])
|
||
|
asn_object.add_attribute('country', value=cen_as['country_code'])
|
||
|
asn_object.add_reference(attribute.uuid, 'associated-to')
|
||
|
misp_event.add_object(**asn_object)
|
||
|
|
||
|
if "ip" in censys_output and "ports" in censys_output:
|
||
|
ip_object = MISPObject('ip-port')
|
||
|
ip_object.add_attribute('ip', value=censys_output['ip'])
|
||
|
for p in censys_output['ports']:
|
||
|
ip_object.add_attribute('dst-port', value=p)
|
||
|
ip_object.add_reference(attribute.uuid, 'associated-to')
|
||
|
misp_event.add_object(**ip_object)
|
||
|
|
||
|
# We explore all ports to find https or ssh services
|
||
|
for k in censys_output.keys():
|
||
|
if not isinstance(censys_output[k], dict):
|
||
|
continue
|
||
|
if 'https' in censys_output[k]:
|
||
|
try:
|
||
|
cert = censys_output[k]['https']['tls']['certificate']
|
||
|
cert_obj = get_certificate_object(cert, attribute)
|
||
|
misp_event.add_object(**cert_obj)
|
||
|
except KeyError:
|
||
|
print("Error !")
|
||
|
if 'ssh' in censys_output[k]:
|
||
|
try:
|
||
|
cert = censys_output[k]['ssh']['v2']['server_host_key']
|
||
|
# To enable once the type is merged
|
||
|
# misp_event.add_attribute(type='hasshserver-sha256', value=cert['fingerprint_sha256'])
|
||
|
except KeyError:
|
||
|
pass
|
||
|
|
||
|
# Info from certificate query
|
||
|
if "parsed" in censys_output:
|
||
|
cert_obj = get_certificate_object(censys_output, attribute)
|
||
|
misp_event.add_object(**cert_obj)
|
||
|
|
||
|
# Location can be present for IP/Websites results
|
||
|
if "location" in censys_output:
|
||
|
loc_obj = MISPObject('geolocation')
|
||
|
loc = censys_output['location']
|
||
|
loc_obj.add_attribute('latitude', value=loc['latitude'])
|
||
|
loc_obj.add_attribute('longitude', value=loc['longitude'])
|
||
|
loc_obj.add_attribute('city', value=loc['city'])
|
||
|
loc_obj.add_attribute('country', value=loc['country'])
|
||
|
loc_obj.add_attribute('zipcode', value=loc['postal_code'])
|
||
|
if 'province' in loc:
|
||
|
loc_obj.add_attribute('region', value=loc['province'])
|
||
|
loc_obj.add_reference(attribute.uuid, 'associated-to')
|
||
|
misp_event.add_object(**loc_obj)
|
||
|
|
||
|
event = json.loads(misp_event.to_json())
|
||
|
return {'Object': event['Object'], 'Attribute': event['Attribute']}
|
||
|
|
||
|
|
||
|
def get_certificate_object(cert, attribute):
|
||
|
parsed = cert['parsed']
|
||
|
cert_object = MISPObject('x509')
|
||
|
cert_object.add_attribute('x509-fingerprint-sha256', value=parsed['fingerprint_sha256'])
|
||
|
cert_object.add_attribute('x509-fingerprint-sha1', value=parsed['fingerprint_sha1'])
|
||
|
cert_object.add_attribute('x509-fingerprint-md5', value=parsed['fingerprint_md5'])
|
||
|
cert_object.add_attribute('serial-number', value=parsed['serial_number'])
|
||
|
cert_object.add_attribute('version', value=parsed['version'])
|
||
|
cert_object.add_attribute('subject', value=parsed['subject_dn'])
|
||
|
cert_object.add_attribute('issuer', value=parsed['issuer_dn'])
|
||
|
cert_object.add_attribute('validity-not-before', value=isoparse(parsed['validity']['start']))
|
||
|
cert_object.add_attribute('validity-not-after', value=isoparse(parsed['validity']['end']))
|
||
|
cert_object.add_attribute('self_signed', value=parsed['signature']['self_signed'])
|
||
|
cert_object.add_attribute('signature_algorithm', value=parsed['signature']['signature_algorithm']['name'])
|
||
|
|
||
|
cert_object.add_attribute('pubkey-info-algorithm', value=parsed['subject_key_info']['key_algorithm']['name'])
|
||
|
|
||
|
if 'rsa_public_key' in parsed['subject_key_info']:
|
||
|
pub_key = parsed['subject_key_info']['rsa_public_key']
|
||
|
cert_object.add_attribute('pubkey-info-size', value=pub_key['length'])
|
||
|
cert_object.add_attribute('pubkey-info-exponent', value=pub_key['exponent'])
|
||
|
hex_mod = codecs.encode(base64.b64decode(pub_key['modulus']), 'hex').decode()
|
||
|
cert_object.add_attribute('pubkey-info-modulus', value=hex_mod)
|
||
|
|
||
|
if "extensions" in parsed and "subject_alt_name" in parsed["extensions"]:
|
||
|
san = parsed["extensions"]["subject_alt_name"]
|
||
|
if "dns_names" in san:
|
||
|
for dns in san['dns_names']:
|
||
|
cert_object.add_attribute('dns_names', value=dns)
|
||
|
if "ip_addresses" in san:
|
||
|
for ip in san['ip_addresses']:
|
||
|
cert_object.add_attribute('ip', value=ip)
|
||
|
|
||
|
if "raw" in cert:
|
||
|
cert_object.add_attribute('raw-base64', value=cert['raw'])
|
||
|
|
||
|
cert_object.add_reference(attribute.uuid, 'associated-to')
|
||
|
return cert_object
|
||
|
|
||
|
|
||
|
def introspection():
|
||
|
return mispattributes
|
||
|
|
||
|
|
||
|
def version():
|
||
|
moduleinfo['config'] = moduleconfig
|
||
|
return moduleinfo
|