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

Squid - firewall rules sanitization #289

Closed
wants to merge 5 commits into from

Conversation

doktornotor
Copy link
Contributor

@doktornotor doktornotor commented Feb 3, 2017

Do NOT merge now, this is for discussion ATM.

OK, guys... so, reading the code, it appears to me the current firewall rules generated by Squid do not make particularly good sense. So I've come up with this:

  • use rdr pass to pass traffic when we have transparent proxy enabled and are doing NAT
  • when non-transparent (case 'rule':) we should pass the traffic to the configured proxy ports only, and the transparent proxy ports/interfaces really have nothing to do with those rules, so no idea why's the code referencing them at all
  • finally, the PPPoE cases - no clue about this, no way to test, never used. Regardless, the current code there appears to be complete nonsense.

For a quick idea, here's a comparison of rules generated by the old and new code. with a really simple setup

  • LAN = igb0_vlan10 being a non-transparent interface
  • WLAN = igb0_vlan88 being set up as transparent interface with SSL/MITM
  • proxy ports default 3128/3129
  • RFC1918 excluded from proxying

Old rules

no rdr on igb0_vlan88 inet proto tcp from any to 192.168.0.0/16 port = http
no rdr on igb0_vlan88 inet proto tcp from any to 192.168.0.0/16 port = https
no rdr on igb0_vlan88 inet proto tcp from any to 172.16.0.0/12 port = http
no rdr on igb0_vlan88 inet proto tcp from any to 172.16.0.0/12 port = https
no rdr on igb0_vlan88 inet proto tcp from any to 10.0.0.0/8 port = http
no rdr on igb0_vlan88 inet proto tcp from any to 10.0.0.0/8 port = https

rdr on igb0_vlan88 inet proto tcp from any to ! (igb0_vlan88) port = http -> 127.0.0.1 port 3128
rdr on igb0_vlan88 inet proto tcp from any to ! (igb0_vlan88) port = https -> 127.0.0.1 port 3129

pass in quick on igb0_vlan88 proto tcp from any to ! (igb0_vlan88) port = http flags S/SA keep state
pass in quick on igb0_vlan88 proto tcp from any to ! (igb0_vlan88) port = https flags S/SA keep state
pass in quick on igb0_vlan88 proto tcp from any to ! (igb0_vlan88) port = 3128 flags S/SA keep state
pass in quick on igb0_vlan88 proto tcp from any to ! (igb0_vlan88) port = 3129 flags S/SA keep state

New rules

no rdr on igb0_vlan88 inet proto tcp from any to 192.168.0.0/16 port = http
no rdr on igb0_vlan88 inet proto tcp from any to 192.168.0.0/16 port = https
no rdr on igb0_vlan88 inet proto tcp from any to 172.16.0.0/12 port = http
no rdr on igb0_vlan88 inet proto tcp from any to 172.16.0.0/12 port = https
no rdr on igb0_vlan88 inet proto tcp from any to 10.0.0.0/8 port = http
no rdr on igb0_vlan88 inet proto tcp from any to 10.0.0.0/8 port = https

rdr pass on igb0_vlan88 inet proto tcp from any to ! (igb0_vlan88) port = http -> 127.0.0.1 port 3128
rdr pass on igb0_vlan88 inet proto tcp from any to ! (igb0_vlan88) port = https -> 127.0.0.1 port 3129

pass in quick on igb0_vlan10 proto tcp from any to (igb0_vlan10) port = 3128 flags S/SA keep state
pass in quick on igb0_vlan88 proto tcp from any to (igb0_vlan88) port = 3128 flags S/SA keep state
pass in quick on lo0 proto tcp from any to (lo0) port = 3128 flags S/SA keep state
pass in quick on igb0_vlan10 proto tcp from any to (igb0_vlan10) port = 3129 flags S/SA keep state
pass in quick on igb0_vlan88 proto tcp from any to (igb0_vlan88) port = 3129 flags S/SA keep state
pass in quick on lo0 proto tcp from any to (lo0) port = 3129 flags S/SA keep state

Also added some comments to the individual changes below.

$PPTP_ALIAS = "\$pptp";
} else {
$PPTP_ALIAS = "\$PPTP";
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed some unused PPTP leftovers.

if (strstr($fw_aliases, "PPPoE =")) {
$PPPOE_ALIAS = "\$PPPoE";
} else {
$PPPOE_ALIAS = "\$pppoe";
}

// define ports based on transparent options and ssl filtering
$pf_rule_port = ($squid_conf['ssl_proxy'] == "on" ? "{80,443}" : "80");
$pf_nat_ports = ($squid_conf['ssl_proxy'] == "on" ? "{80,443}" : "80");
$pf_rule_ports = "{{$port},{$ssl_port}}";
Copy link
Contributor Author

@doktornotor doktornotor Feb 3, 2017

Choose a reason for hiding this comment

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

  • $pf_nat_ports is for transparent proxy
  • $pf_rule_ports is for the defined proxy interfaces (nontransparent) - 3128/3129 by default

(with, or without HTTPS/SSL Interception)

  • $pf_transparent_rule_port - always 80, plus 443 if the interface is selected under SSL Intercept Interface(s)

if (in_array($t_iface, $ssl_ifaces)) {
$rules .= "rdr on $t_iface proto tcp from any to !($t_iface) port 443 -> 127.0.0.1 port {$ssl_port}\n";
$rules .= "rdr pass on $t_iface proto tcp from any to !($t_iface) port 443 -> 127.0.0.1 port {$ssl_port}\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Use rdr pass to pass traffic destined to 443 to the transparent proxy

}
}
foreach ($transparent_ifaces as $t_iface) {
$pf_transparent_rule_port = (in_array($t_iface, $ssl_ifaces) ? "{80,443}" : "80");
$rules .= "rdr on $t_iface proto tcp from any to !($t_iface) port 80 -> 127.0.0.1 port {$port}\n";
$rules .= "rdr pass on $t_iface proto tcp from any to !($t_iface) port 80 -> 127.0.0.1 port {$port}\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Use rdr pass to pass traffic destined to 80 to the transparent proxy

}
}
/* Handle PPPOE case */
if (($config['pppoe']['mode'] == "server" && $config['pppoe']['localip']) || (function_exists("is_pppoe_server_enabled") && is_pppoe_server_enabled())) {
$rules .= "rdr on $PPPOE_ALIAS proto tcp from any to !127.0.0.1 port {$pf_rule_port} -> 127.0.0.1 port {$port}\n";
Copy link
Contributor Author

@doktornotor doktornotor Feb 3, 2017

Choose a reason for hiding this comment

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

Well, this is really WTF. No idea what it was supposed to do, plus why limited to the HTTP proxy port only?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's for local clients that connected using the PPPoE server. Not sure why it's HTTP only but it's necessary so they could be proxied. The way mpd works it uses a bunch of different local interfaces in a group. There is some code higher up that sets PPPOE_ALIAS conditionally that is no longer needed. It's only $pppoe now.

Copy link
Contributor Author

@doktornotor doktornotor Feb 7, 2017

Choose a reason for hiding this comment

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

OK, re-added that (below) with the !127.0.0.1 destination, it also does HTTPS now when SSL proxy is enabled, not just HTTP.

$rules .= "# Setup squid pass rules for proxy\n";
$rules .= "pass in quick on $iface proto tcp from any to !($iface) port {$pf_transparent_rule_port} flags S/SA keep state\n";
// $rules .= "pass in quick on $iface proto tcp from any to !($iface) port {$port} flags S/SA keep state\n";
$rules .= "pass in quick on $iface proto tcp from any to !127.0.0.1 port {$pf_rule_ports} flags S/SA keep state\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pass traffic for the proxy ports (3128/3129 by default on each proxy interface)

$rules .= "\n";
}
if ($config['pppoe']['mode'] == "server" && $config['pppoe']['localip']) {
$rules .= "pass in quick on $PPPOE_ALIAS proto tcp from any to !127.0.0.1 port {$port} flags S/SA keep state\n";
$rules .= "pass in quick on $PPPOE_ALIAS proto tcp from any to !127.0.0.1 port {$pf_rule_ports} flags S/SA keep state\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto for PPPoE.

@@ -2095,7 +2091,7 @@ function squid_generate_rules($type) {
}
/* Handle PPPOE case */
if (($config['pppoe']['mode'] == "server" && $config['pppoe']['localip']) || (function_exists("is_pppoe_server_enabled") && is_pppoe_server_enabled())) {
$rules .= "no rdr on $PPPOE_ALIAS proto tcp from any to { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 } port {$pf_rule_port}\n";
$rules .= "no rdr on $PPPOE_ALIAS proto tcp from any to { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 } port {$pf_nat_ports}\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do not redirect these if 'Bypass Proxy for Private Address Destination' is checked.

@@ -2117,7 +2113,7 @@ function squid_generate_rules($type) {
}
/* Handle PPPOE case */
if (($config['pppoe']['mode'] == "server" && $config['pppoe']['localip']) || (function_exists("is_pppoe_server_enabled") && is_pppoe_server_enabled())) {
$rules .= "no rdr on $PPPOE_ALIAS proto tcp from { $exempt_ip } to any port {$pf_rule_port}\n";
$rules .= "no rdr on $PPPOE_ALIAS proto tcp from { $exempt_ip } to any port {$pf_nat_ports}\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do not redirect 'Bypass Proxy for These Source IPs'

@@ -2139,33 +2135,33 @@ function squid_generate_rules($type) {
}
/* Handle PPPOE case */
if (($config['pppoe']['mode'] == "server" && $config['pppoe']['localip']) || (function_exists("is_pppoe_server_enabled") && is_pppoe_server_enabled())) {
$rules .= "no rdr on $PPPOE_ALIAS proto tcp from any to { $exempt_dest } port {$pf_rule_port}\n";
$rules .= "no rdr on $PPPOE_ALIAS proto tcp from any to { $exempt_dest } port {$pf_nat_ports}\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do not redirect 'Bypass Proxy for These Destination IPs'

}
}
foreach ($transparent_ifaces as $t_iface) {
$pf_transparent_rule_port = (in_array($t_iface, $ssl_ifaces) ? "{80,443}" : "80");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed, was not used for anything here.

}
$rules .= "\n";
break;
case 'filter':
case 'rule':
foreach ($transparent_ifaces as $iface) {
$pf_transparent_rule_port = (in_array($iface, $ssl_ifaces) ? "{80,443,{$port},{$ssl_port}}" : "{80,{$port}}");
foreach ($proxy_ifaces as $iface) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This really is not about transparent proxy interfaces; rdr pass does the job there.

$rules .= "# Setup squid pass rules for proxy\n";
$rules .= "pass in quick on $iface proto tcp from any to !($iface) port {$pf_transparent_rule_port} flags S/SA keep state\n";
// $rules .= "pass in quick on $iface proto tcp from any to !($iface) port {$port} flags S/SA keep state\n";
$rules .= "pass in quick on $iface proto tcp from any to ($iface) port {$pf_rule_ports} flags S/SA keep state\n";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pass traffic to the interface IP only

$rules .= "\n";
}
if ($config['pppoe']['mode'] == "server" && $config['pppoe']['localip']) {
$rules .= "pass in quick on $PPPOE_ALIAS proto tcp from any to !127.0.0.1 port {$port} flags S/SA keep state\n";
$rules .= "pass in quick on $PPPOE_ALIAS proto tcp from any to ($PPPOE_ALIAS) port {$pf_rule_ports} flags S/SA keep state\n";
Copy link
Contributor Author

@doktornotor doktornotor Feb 3, 2017

Choose a reason for hiding this comment

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

Pass traffic to the interface IP only. I guess this should really be
$rules .= "pass in quick on $PPPOE_ALIAS proto tcp from any to {$config['pppoe']['localip']} port {$pf_rule_ports} flags S/SA keep state\n"; for PPPoE???

Copy link
Contributor

Choose a reason for hiding this comment

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

Given that it's a group I'm not certain how pf treats using that as a destination. Using the configured local IP address would be safer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, you mean something like

pass in quick on $pppoe proto tcp from any to ($pppoe) port {$pf_rule_ports} ...

?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say this is safer:
pass in quick on $pppoe proto tcp from any to {$config['pppoe']['localip']} port {$pf_rule_ports}
But I'd have to try it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I really need someone with a working PPPoE setup to test whatever is figured out here. So, to summarize so far:

  • nuke the weird $PPPOE_ALIAS everywhere and use $pppoe instead
  • use {$config['pppoe']['localip']} as destination

Right?

Copy link
Contributor

Choose a reason for hiding this comment

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

That looks right.
I have two VMs with PPPoE server setup but they're acting as "WAN" connections for other pfSense VMs so I can test PPPoE WANs, it's easy to setup and test, there's not much to it. Should work just as easy for clients as those.

$rules .= "rdr pass on $PPPOE_ALIAS proto tcp from any to !($PPPOE_ALIAS) port 80 -> 127.0.0.1 port {$port}\n";
if ($squid_conf['ssl_proxy'] == "on") {
$rules .= "rdr pass on $t_iface proto tcp from any to !($PPPOE_ALIAS) port 443 -> 127.0.0.1 port {$ssl_port}\n";
}
Copy link
Contributor Author

@doktornotor doktornotor Feb 3, 2017

Choose a reason for hiding this comment

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

I guess this should really be
$rules .= "rdr pass on $PPPOE_ALIAS proto tcp from any to !{$config['pppoe']['localip']} port ...
for both PPPoE lines above???

@rbgarga rbgarga requested review from rbgarga and jim-p February 7, 2017 11:17
@jim-p
Copy link
Contributor

jim-p commented Feb 7, 2017

Is the "old rules" example above right? You show rdr pass in there already.

@doktornotor
Copy link
Contributor Author

Kinda unsure what's the question. No, there's no rdr pass currently in Squid.

@jim-p
Copy link
Contributor

jim-p commented Feb 7, 2017

In the initial comment on the PR you put two boxes of rules "comparison of rules generated by the old and new code", the one labeled "Old rules" has rdr pass lines in it.

@doktornotor
Copy link
Contributor Author

Eh... probably pasted something wrong there. The new ones is correct, cannot check any old ones at the moment. There's no rdr pass anywhere there, though.

@jim-p
Copy link
Contributor

jim-p commented Feb 7, 2017

That's what I figured, but I wanted to be certain.

@doktornotor
Copy link
Contributor Author

doktornotor commented Feb 7, 2017

Yeah, I'll fix that part as soon as I can revert the file somewhere and re-check, but the main point of why I started investigating this was that the pass rules created are way too permissive, they just basically let HTTP/HTTPS pass through altogether except if the proxy interface is the destination, and that's impossible to override by normal firewall rules since it's a quick one. I mean this line:

https://github.com/pfsense/FreeBSD-ports/blob/devel/www/pfSense-pkg-squid/files/usr/local/pkg/squid.inc#L2163

@doktornotor
Copy link
Contributor Author

doktornotor commented Feb 7, 2017

@jim-p - Fixed the "old rules" in the first comment after reverting to squid.inc from 0.4.35_3; so yeah

  • there's no rdr pass anywhere.
  • for transparent interfaces, it's passing (with quick) 80/443 (plus 3128/3129) to everywhere except the proxy interface IP for god knows what reason.
  • for non-transparent ones, it's not even passing the required traffic to proxy (just a non-issue for most users due to the widely permissive rules on LANs, usually allow any).

@@ -2095,7 +2084,7 @@ function squid_generate_rules($type) {
}
/* Handle PPPOE case */
if (($config['pppoe']['mode'] == "server" && $config['pppoe']['localip']) || (function_exists("is_pppoe_server_enabled") && is_pppoe_server_enabled())) {
$rules .= "no rdr on $PPPOE_ALIAS proto tcp from any to { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 } port {$pf_rule_port}\n";
$rules .= "no rdr on $pppoe proto tcp from any to { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 } port {$pf_nat_ports}\n";
Copy link
Member

Choose a reason for hiding this comment

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

I didn't see where $pppoe is being defined

Copy link
Contributor Author

@doktornotor doktornotor Feb 8, 2017

Choose a reason for hiding this comment

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

@rbgarga - the changes here were done as suggested by @jim-p (#289 (comment)) - as I've noted, I don't have any such setup to test, and generally this is something completely outside of my knowledge. Perhaps this should be \$pppoe instead, NFC.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's not a PHP variable but a pf macro that will be in the ruleset already if the pppoe server is enabled (which the if condition above tests for).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So you want that escaped?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, it needs to be escaped

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, done... I guess we are slowly getting there.

(I'd prefer to do a new PR when we are done here, there's bunch of typo fixes to be done in the package as well, plus no need to have this kind of messing in git history).

@doktornotor
Copy link
Contributor Author

Closing in favor of #305 - all review comments are included there, plus additional fixes.

@doktornotor doktornotor deleted the patch-3 branch February 14, 2017 10:29
netgate-git-updates pushed a commit that referenced this pull request Aug 15, 2018
  [ Robert Edmonds ]
  * Release 1.3.1.

  * Restore protobuf-2.x compatibility (#284, #285).

  * Use xenial and protobuf 3.6.1 in the Travis-CI environment (#332).

  * Convert uses of protobuf's scoped_ptr.h to C++11 std::unique_ptr, needed
    to compile against protobuf 3.6.1 (#320, #333).

  * Use AX_CXX_COMPILE_STDCXX macro to enable C++11 support in old compilers
    (#312, #317, #327, #334).

  [ Fredrik Gustafsson ]
  * Add std:: to some types (#294, #305, #309).

  [ Sam Collinson ]
  * Check the return value of int_range_lookup before using as an array
    index; it can return -1 (#315).

  [ Matthias Dittrich ]
  * Fix compilation on mingw by using explicit protoc --plugin=NAME=PATH
    syntax in Makefile.am (#289, #290).

Sponsored by:	Farsight Security, Inc.
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.

4 participants