From 4c383fa01faf74a3035bd5585d46629952c95545 Mon Sep 17 00:00:00 2001 From: Hunter DeMeyer Date: Tue, 16 May 2017 10:49:03 -0400 Subject: [PATCH 1/3] Add ability to delete user config I added the option --delete-config which allows the user to delete a configuration directory. By default, the directory of the user who invoked the script is deleted. This behavior can be changed to remove the configuration of any user by passing a username to the --delete-config option. --- tools/uninstall.sh | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/tools/uninstall.sh b/tools/uninstall.sh index 41a231db..db7a2387 100755 --- a/tools/uninstall.sh +++ b/tools/uninstall.sh @@ -7,6 +7,7 @@ # Project Home Page: https://github.com/securestate/king-phisher/ # Authors: # Erik Daguerre +# Hunter DeMeyer # ############################################################################### @@ -57,11 +58,12 @@ function show_help { echo "King Phisher uninstall Script" echo "" echo "optional arguments" - echo " -h, --help show this help message and exit" - echo " -n, --no answer no to all questions" - echo " -y, --yes answer yes to all questions" - echo " --delete-database delete the King Phisher database" - echo " --delete-directory delete the King Phisher directory" + echo " -h, --help show this help message and exit" + echo " -n, --no answer no to all questions" + echo " -y, --yes answer yes to all questions" + echo " --delete-database delete the King Phisher database" + echo " --delete-directory delete the King Phisher directory" + echo " --delete-config [USER] delete the King Phisher configuration, optionally for USER" return 0; } @@ -91,6 +93,20 @@ while :; do --delete-directory) KING_PHISHER_DELETE_DIRECTORY="x" ;; + --delete-config) + KING_PHISHER_DELETE_CONFIG="x" + user="$(logname)" + if [ ! -z $2 ]; then + if [ ! "$(echo $2 | cut -c1)" == "-" ]; then + if [ ! "$(id -u $2)" == "" ]; then + user="$2" + else + exit $E_USAGE + fi + fi + fi + ;; + --) shift break @@ -140,6 +156,15 @@ if [ ! -z $KING_PHISHER_DELETE_DIRECTORY ]; then fi fi +if [ ! -z $KING_PHISHER_DELETE_CONFIG ]; then + echo "Warning: The configuration for $user will be removed" + prompt_yes_or_no "Are you sure you want to continue?" "no" KING_PHISHER_DELETE_CONFIRM + if [ "$KING_PHISHER_DELETE_CONFIRM" == "yes" ]; then + rm -rf "/home/$user/.config/king-phisher" + echo "The King Phisher configuration for $user has been removed" + fi +fi + if [ -f /lib/systemd/system/king-phisher.service ]; then if ! systemctl is-active king-phisher; then systemctl stop king-phisher From a0323bda8ca152df873548670d3ae9d49c32ee4d Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 17 May 2017 17:03:34 -0400 Subject: [PATCH 2/3] Search additional data when checking SPF mechanisms --- king_phisher/spf.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/king_phisher/spf.py b/king_phisher/spf.py index 36de9753..d82daee2 100644 --- a/king_phisher/spf.py +++ b/king_phisher/spf.py @@ -265,7 +265,7 @@ def check_host(self): def _check_host(self, ip, domain, sender, top_level=True): try: - answers = self._dns_query(domain, 'TXT') + answers, _ = self._dns_query(domain, 'TXT') except SPFTempError: if not top_level: raise @@ -324,6 +324,7 @@ def _check_host(self, ip, domain, sender, top_level=True): return SPFResult.NEUTRAL def _dns_query(self, qname, qtype): + # returns answers, additional self.query_limit -= 1 if self.query_limit < 0: raise SPFPermError('DNS query limit reached') @@ -342,7 +343,7 @@ def _dns_query(self, qname, qtype): answers = [] for answer in response.answer: answers.extend(answer.items) - return answers + return answers, response.additional def _evaluate_mechanism(self, ip, domain, sender, mechanism, rvalue): if rvalue is None: @@ -356,7 +357,8 @@ def _evaluate_mechanism(self, ip, domain, sender, mechanism, rvalue): elif mechanism == 'all': return True elif mechanism == 'exists': - if len(self._dns_query(rvalue, 'A')): + answers, _ = self._dns_query(rvalue, 'A') + if len(answers): return True elif mechanism == 'include': # pass results in match per https://tools.ietf.org/html/rfc7208#section-5.2 @@ -380,9 +382,13 @@ def _evaluate_mechanism(self, ip, domain, sender, mechanism, rvalue): if ip in ip_network: return True elif mechanism == 'mx': - for mx_record in self._dns_query(rvalue, 'MX'): + answers, additional = self._dns_query(rvalue, 'MX') + for mx_record in answers: mx_record = str(mx_record.exchange).rstrip('.') - if self._hostname_matches_ip(ip, mx_record): + found, matches = self._hostname_matches_additional(ip, mx_record, additional) + if matches: + return True + if not found and self._hostname_matches_ip(ip, mx_record): return True elif mechanism == 'ptr': if isinstance(ip, ipaddress.IPv4Address): @@ -395,7 +401,8 @@ def _evaluate_mechanism(self, ip, domain, sender, mechanism, rvalue): ip = ip.split('.') ip.reverse() ip = '.'.join(ip) - for ptr_record in self._dns_query(ip + '.' + suffix + '.arpa', 'PTR'): + answers, _ = self._dns_query(ip + '.' + suffix + '.arpa', 'PTR') + for ptr_record in answers: ptr_record = str(ptr_record.target).rstrip('.') if ptr_domain == ptr_record or ptr_domain.endswith('.' + ptr_record): return True @@ -403,9 +410,30 @@ def _evaluate_mechanism(self, ip, domain, sender, mechanism, rvalue): raise SPFPermError("unsupported mechanism type: '{0}'".format(mechanism)) return False + def _hostname_matches_additional(self, ip, name, additional): + """ + Search for *name* in *additional* and if it is found, check that it + includes *ip*. + + :param ip: The IP address to search for. + :type ip: :py:class:`ipaddress.IPv4Address`, :py:class:`ipaddress.IPv6Address` + :param str name: The name to search for. + :param tuple additional: The additional data returned from a dns query to search in. + :return: The first value is whether or not *name* was found in *additional*, the second is if *ip* was also found. + :rtype: tuple + """ + rdtype = (1 if isinstance(ip, ipaddress.IPv4Address) else 28) + ip = str(ip) + additional = (entry for entry in additional if entry.rdtype == rdtype) + entry = next((entry for entry in additional if str(entry.name)[:-1] == name), None) + if entry is None: + return False, None + item = next((item for item in entry.items if item.address == ip), None) + return True, item is not None + def _hostname_matches_ip(self, ip, name): qtype = ('A' if isinstance(ip, ipaddress.IPv4Address) else 'AAAA') - answers = self._dns_query(name, qtype) + answers, _ = self._dns_query(name, qtype) return str(ip) in tuple(a.address for a in answers) def expand_macros(self, value, ip, domain, sender): From 1f481dbaf30c646d6c98c0a71a3ab730990fad0c Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 17 May 2017 17:21:01 -0400 Subject: [PATCH 3/3] Support optionally limiting SPF void lookups --- docs/source/king_phisher/spf.rst | 2 ++ king_phisher/spf.py | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/source/king_phisher/spf.rst b/docs/source/king_phisher/spf.rst index 683b8693..0ff444a4 100644 --- a/docs/source/king_phisher/spf.rst +++ b/docs/source/king_phisher/spf.rst @@ -15,6 +15,8 @@ Data .. autodata:: king_phisher.spf.MAX_QUERIES +.. autodata:: king_phisher.spf.MAX_QUERIES_VOID + .. autodata:: king_phisher.spf.QUALIFIERS :annotation: diff --git a/king_phisher/spf.py b/king_phisher/spf.py index d82daee2..0a42262b 100644 --- a/king_phisher/spf.py +++ b/king_phisher/spf.py @@ -53,6 +53,11 @@ The maximum number of DNS queries allowed to take place during evaluation as defined within section 4.6.4 of :rfc:`7208`. """ +MAX_QUERIES_VOID = float('inf') +""" +The maximum number of DNS queries allowed to either return with rcode 0 and no +answers or rcode 3 (Name Error) as defined within section 4.6.4 of :rfc:`7208`. +""" QUALIFIERS = { '+': SPFResult.PASS, @@ -235,6 +240,7 @@ def __init__(self, ip, domain, sender=None): """ # dns lookup limit per https://tools.ietf.org/html/rfc7208#section-4.6.4 self.query_limit = MAX_QUERIES + self.query_limit_void = MAX_QUERIES_VOID self.policy = None """ The human readable policy result, one of the @@ -341,6 +347,11 @@ def _dns_query(self, qname, qtype): if rcode not in (dns.rcode.NOERROR, dns.rcode.NXDOMAIN): raise SPFTempError("DNS resolution error for: {0} {1} (rcode: {2})".format(qname, qtype, rcode)) answers = [] + if len(response.answer) == 0 or rcode == dns.rcode.NXDOMAIN: + self.logger.debug("resolving {0:<3} record for {1} using nameserver {2} resulted in a void lookup".format(qtype, qname, nameservers[0])) + self.query_limit_void -= 1 + if self.query_limit_void < 0: + raise SPFPermError('DNS query void lookup limit reached') for answer in response.answer: answers.extend(answer.items) return answers, response.additional