Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 62 restrict interfaces #77

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open

Conversation

mlhaigh
Copy link
Contributor

@mlhaigh mlhaigh commented May 3, 2018

Issue #62 complete.

Copy link
Collaborator

@strictlymike strictlymike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent time with this today and this is largely what we specified. But, I found a few items worth discussing that don't correspond to any existing code line, so I'll share them here.

Most significantly, testing revealed that in single-host mode, the test script sporadically originates packets on a blacklisted interface because the networking/routing stack has decided that this is the best interface to get to the arbitrary/non-existent remote IP from the test script. The packet is passed (or dropped) without diverting it to FakeNet-NG. In a real use case, this would manifest itself as malware samples sporadically not receiving responses. I don't know how to fix this except to limit the use of this setting to MultiHost mode where it can't conflict with this behavior. So I think we have to do that.

I'd also like for us to add these new configuration settings to the test file template.ini to ensure the usual tests run ok with the default settings, too. The default is to turn off blacklisted interfaces, I just want the configuration code to be exercised during testing to detect any issues with that in the future.

Lastly: Should the original packet be written to the pcap when it involves a blacklisted interface? (see the call to self.write_pcap() in handle_pkt() of DiverterBase) From a high level, the purpose of blacklisted interfaces is to allow administrative access to FakeNet-NG hosts, and such traffic is not part of the fake little malware world we're trying to create and study when we run FakeNet-NG. I'm open to a discussion on this. I'd like to try to do what the user would expect, but without adding additional complexity with a configuration setting if possible.

pkt.dst_ip in self.blacklist_ifaces)):
self.logger.debug("Blacklisted Interface. src: %s dst: %s" %
(pkt.src_ip, pkt.dst_ip))
if self.blacklist_ifaces_disp == 'Drop':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use .lower() and compare with lowercase 'drop' to ensure that if the default value from diverterbase.py:1083 is used (either automatically or by the user), it works.

@@ -1141,12 +1152,33 @@ def handle_pkt(self, pkt, callbacks3, callbacks4):

crit = DivertParms(self, pkt)

# check for blacklisted interface and drop if needed
if (self.blacklist_ifaces and
(pkt.src_ip in self.blacklist_ifaces or
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like blacklist_ifaces will just be the raw string specified in the config which is a comma-delimited list of IPs. That being so, this line will merely check if the source IP is a substring of the list. On a system 11 interfaces with IPs 1.2.3.1 through 1.2.3.11, if the user specifies to pass packets involving 1.2.3.1, won't this code also inadvertently pass packets involving 1.2.3.10 and 1.2.3.11 because of the substring match? I'll revisit the comma-delimited case separately because I believe it also will pose issues.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a concrete example, from typing into the python interpreter:

>>> '1.2.3.1' in '1.2.3.10'
True

self.blacklist_ifaces_disp = (
self.getconfigval('linuxblacklistinterfacesdisposition', 'drop'))
self.blacklist_ifaces = (
self.getconfigval('linuxblacklistedinterfaces', None))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This takes the raw string that the user typed and stashes it in self.blacklist_ifaces as a str object. If the user specifies a comma-separated list of IP addresses here, then self.blacklist_ifaces will just be that string, e.g. 1.2.3.1, 1.2.3.5. Because the pkt.src_ip in self.blacklist_ifaces check can match in cases where it should not (see below) and must be replaced with a new check, I suggest modifying the code here to split these on commas/whitespace and store them as an array or a set, and then using set intersection (see https://docs.python.org/2/library/sets.html) to test if the set of source/dest IPs overlaps with the set of blacklisted interface IPs.

# there needs to be no per-packet output by default. If there is
# output for each packet, an infinite loop is generated where each
# packet produces output which produces a packet, etc.
elif (pid and (pid != self.pid) and crit.first_packet_new_session &
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you use a boolean & operator instead of the logical and keyword at the tail end of this line?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably yielding inconsistent results from what you expected because of the varying operator precedence of & versus and, with respect to is not:

Result Values With and With &
same [False, False, False] False (and) False (&)
DIFF [True, False, False] False (and) True (&)
same [False, True, False] False (and) False (&)
same [True, True, False] True (and) True (&)
same [False, False, True] False (and) False (&)
DIFF [True, False, True] False (and) True (&)
same [False, True, True] False (and) False (&)
same [True, True, True] False (and) False (&)

# output for each packet, an infinite loop is generated where each
# packet produces output which produces a packet, etc.
elif (pid and (pid != self.pid) and crit.first_packet_new_session &
no_further_processing is not True):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or more succinctly, not no_further_processing (even though it reads as a double negative in English).

@@ -242,7 +242,7 @@ def handle_nonlocal(self, nfqpkt):
self.logger.error('Exception: %s' % (traceback.format_exc()))
raise

nfqpkt.accept()
nfqpkt.accept() if not pkt.drop else nfqpkt.drop()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I agree with @tankbusta that if / else would be more readable:

if pkt.drop:
    nfqpkt.drop()
else:
    nfqpkt.accept()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants