Tip

If you find some discrepancy or missing information - open a GitHub issue

Was this information useful? Then star the repository on GitHub

Warning

Writing of this documentation is still in progress! Read it with a grain of salt!

Proxy - Squid

Warning

This application let’s you intercept and modify network traffic.

That can be illegal => you are warned.


Introduction

Whenever we are referring to a ‘client’ - it will be a server, workstation or network device in most cases.


Setup

Manual

Docker

You can build a docker image as seen in this repository!


References

  • Config examples (WARNING: some examples are deprecated and will not work on current versions)


Installation

SSL

If you are only ‘peaking’ at SSL connections - this should be enough:

sudo apt install squid-openssl  # the package needs to have ssl-support enabled at compile-time

openssl dhparam -outform PEM -out /etc/squid/ssl_bump.dh.pem 2048

# openssl create self-signed cert
openssl req -x509 -newkey rsa:4096 -keyout /etc/squid/ssl_bump.key -out /etc/squid/ssl_bump.crt -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=Forward Proxy"

# create ssl cache DB
sudo mkdir -p /var/lib/squid
sudo rm -rf /var/lib/squid/ssl_db
sudo /usr/lib/squid/security_file_certgen -c -s /var/lib/squid/ssl_db -M 20MB
sudo chown -R proxy:proxy /var/lib/squid

If you want to intercept SSL connections (Man-in-the-middle like) - you will have to go through some more steps: squid docs - ssl interception


Modes


HTTP_PORT

The http_port mode can be used as target proxy in applications like browsers.

Usual port 3128 is used for this mode.

The application creates a HTTP-CONNECT tunnel to the proxy and wraps its requests in it.

DNS resolution is done by the proxy.

HTTPS_PORT

Like mode http_mode but the HTTP-CONNECT tunnel is wrapped in TLS.

Usual port 3129 is used for this mode.

For the proxy to be able to handle the DNS resolution - ssl-bump must be configured. Else the proxy will not be able to read the Server-Name-Identifier used in the TLS handshake.

INTERCEPT

In this mode the proxy will expect the plain traffic to arrive.

You will have to create a dedicated listener with ssl-bump enabled if you want to handle TLS traffic.

See also:

SSL-BUMP

SSL-BUMP allows us to:

  • read TLS handshake information

  • intercept (read/modify) TLS traffic

PEAK

By peaking at TLS handshake information in ssl-bump step-1 we are able to gain some important information:

  • target DNS/hostname from SNI

Benefits:

  • less performance needed than full ssl-interception

  • faster than full ssl-interception

  • less problems with applications that check certificates on their end (p.e. banking)

  • no need to create/manage an internal Sub-CA to dynamically create and sign certificates for ssl-intercepted targets

Drawbacks:

  • less options to filter the traffic on

  • connections to trustable targets could carry dangerous payloads

In some cases a basic DNS ‘allow-list’ will be enough to ensure good security. Many automated attacks can be blocked using this approach.

INTERCEPT

This one will be used in zero-trust environments.

See also: Security Stances

Note:

Even incorrectly used TLS usually makes it possible for at least one end of the communication channel to detect the proxies existence. Squid SSL-Bump is intentionally implemented in a way that allows that detection without breaking the TLS. Your clients will be capable of identifying the proxy exists. If you are looking for a way to do it in complete secrecy, dont use Squid.

Benefits:

  • ssl-interception gives us much information that can be used to run IPS/IDS checks on

  • possible dangerous payloads like downloads can be checked by anti-virus

  • more restrictions make even interactive attacks harder to go through

Drawbacks:

  • complex ruleset if you go with an implicit-deny approach

  • much more performance needed

  • increasing latency

  • with a bad ruleset you will still have security-leaks but also have worse performance (lose-lose)


TPROXY

TProxy is a functionality built into current kernels.

It allows us to redirect traffic without modifying it. This solves the issue with overwritten destination-IPs by using Destination-NAT.

The major two integrations of TPROXY we will focus on are the ones in IPTables and NFTables.

In both implementations this is how we will need to handle the three main traffic types:

  • INPUT traffic: can be redirected to TPROXY directly

  • FORWARD traffic: can be redirected to TPROXY directly

  • OUTPUT traffic: needs to be routed to loopback to be redirected to TPROXY

Why do we need to send ‘output’ traffic to loopback? Because TPROXY is only available in the ‘prerouting-filter’ chain and ‘output’ traffic does not hit that one by default.

NFTables

See: NFTables TProxy

IPTables

See: IPTables TPROXY


Config

Config options

  • Matching all subdomains of a Domain can be done by prepending a dot (‘wildcard’ matching)

    Example: ‘.example.com’

    You may not all ‘example.com’ and ‘.example.com’ as it will result in a syntax error

  • You may want to exclude Port-Probes from your logs:

    acl hasRequest has request
    access_log syslog:local2 squid hasRequest
    

You need to define listeners:

See also: Squid documentation - http_port

# clients =HTTP[TCP]=> SQUID =TCP=> TARGET
http_port 3128 ssl-bump tcpkeepalive=60,30,3 cert=/etc/squid/ssl_bump.crt key=/etc/squid/ssl_bump.key cipher=HIGH:MEDIUM:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS tls-dh=prime256v1:/etc/squid/ssl_bump.dh.pem options=NO_SSLv3,NO_TLSv1,SINGLE_DH_USE,SINGLE_ECDH_USE

# clients =HTTPS[TCP]=> SQUID =TCP=> TARGET
https_port 3128 ssl-bump tcpkeepalive=60,30,3 cert=/etc/squid/ssl_bump.crt key=/etc/squid/ssl_bump.key cipher=HIGH:MEDIUM:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS tls-dh=prime256v1:/etc/squid/ssl_bump.dh.pem options=NO_SSLv3,NO_TLSv1,SINGLE_DH_USE,SINGLE_ECDH_USE

# clients =ROUTED TCP=> SQUID =TCP=> TARGET
http_port 3129 intercept
https_port 3130 intercept ssl-bump tcpkeepalive=60,30,3 cert=/etc/squid/ssl_bump.crt key=/etc/squid/ssl_bump.key cipher=HIGH:MEDIUM:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS tls-dh=prime256v1:/etc/squid/ssl_bump.dh.pem options=NO_SSLv3,NO_TLSv1,SINGLE_DH_USE,SINGLE_ECDH_USE

# clients =TPROXY TCP=> SQUID (@127.0.0.1) =TCP=> TARGET
http_port 3129 tproxy
https_port 3130 tproxy ssl-bump tcpkeepalive=60,30,3 cert=/etc/squid/ssl_bump.crt key=/etc/squid/ssl_bump.key cipher=HIGH:MEDIUM:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS tls-dh=prime256v1:/etc/squid/ssl_bump.dh.pem options=NO_SSLv3,NO_TLSv1,SINGLE_DH_USE,SINGLE_ECDH_USE

You can define the IPs Squid should use for outbound traffic. This can be useful to define specific firewall rules for those addresses:

tcp_outgoing_address 192.168.10.2
tcp_outgoing_address 2001:db8::1:2

You may want to cover at least those basic filters:

  • only allow

    • specific destination ports

      acl dest_ports port 80
      acl dest_ports port 443
      acl dest_ports port 587
      http_access deny !dest_ports
      
    • only allow proxy-access from specific source networks

      acl src_internal src 127.0.0.0/8
      acl src_internal src 192.168.0.0/16
      acl src_internal src 172.16.0.0/12
      acl src_internal src 10.0.0.0/8
      http_access deny !src_internal
      
    • only allow access to specific destinations

      • filter on an IP-basis

        acl dst_internal src 192.168.0.0/16
        acl dst_internal src 172.16.0.0/12
        acl dst_internal src 10.0.0.0/8
        http_access allow dst_internal
        http_access deny all
        
      • filter on a DNS-basis (SSL-Bump ‘Peak’ needed)

        acl domains_allowed dstdomain example.com
        acl domains_allowed dstdomain superstes.eu
        http_access allow domains_allowed
        http_access deny all
        
  • check server certificates for issues (expired, untrusted, weak ciphers)

    tls_outgoing_options options=NO_SSLv3,NO_TLSv1,SINGLE_DH_USE,SINGLE_ECDH_USE cipher=HIGH:MEDIUM:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS
    acl ssl_exclude_verify dstdomain .example.com
    sslproxy_cert_error allow ssl_exclude_verify
    sslproxy_cert_error deny all
    
  • enable ssl-bump ‘peaking’

    sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/lib/squid/ssl_db -M 20MB
    
    acl CONNECT method CONNECT
    acl ssl_ports port 443
    acl step1 at_step SslBump1
    
    http_access deny CONNECT !ssl_ports
    http_access allow CONNECT step1  # without 'step1' here one would be able to 'tunnel' unwanted traffic through the proxy
    ssl_bump peek step1 ssl_ports
    ssl_bump splice all
    

Service

To keep invalid configuration from stopping/failing your squid.service - you can add a config-validation in it:

# /etc/systemd/system/squid.service.d/override.conf

[Service]
ExecStartPre=
ExecStartPre=/usr/sbin/squid -k parse
ExecStartPre=/usr/sbin/squid --foreground -z

ExecReload=
ExecReload=/usr/sbin/squid -k parse
ExecReload=/bin/kill -HUP $MAINPID

Restart=on-failure
RestartSec=5s

This will catch and log config-errors before doing a reload/restart.

When doing a system-reboot it will still fail if your config is bad.


Examples


Transparent Proxy

Sometimes setting the environment-variables ‘HTTP_PROXY’, ‘HTTPS_PROXY’, ‘http_proxy’ and ‘https_proxy’ for all applications and HTTP-clients may be problematic/too inconsistent.

An attacker might also be able to modify the environmental variables once a vulnerability has been exploited.

Destination NAT

In some older tutorials and write-ups you will see that people DNAT traffic from a ‘client’ system to a remote proxy server.

This IS NOT SUPPORTED by squid.

It will lead to an error like this: ‘Forwarding loop detected’

Why is that?

Squid’s transparent operation modes DO NOT handle DNS resolution! They instead use the actual destination IP from the IP-headers and send the outgoing traffic to it. That is because of some vulnerability

When using DNAT the destination IP is set to the proxy’s IP. Therefore => loop.

Routed Traffic

You can use this option if the proxy server shares a Layer 2 network with the system that sends or routes the traffic.

Practical examples of this:

  • Network gateway (router) sends traffic to proxy for interception

  • ‘Client’ devices use the proxy as gateway instead of the actual router

In this case we will need to set-up Squid listeners in intercept mode to process the traffic.

You could also use the tproxy mode - but that might be more complicated to set-up when you want to check the traffic that enters at the ‘forwarding’ chain.

Forwarded Traffic

In some situations you will not be able to use the option to route the traffic to the proxy.

This might be because:

  • you are not controlling the gateway/router

  • the ‘client’ device is isolated (only connected to WAN)

  • client and/or network restrictions don’t allow for re-routing the traffic

Practical examples of this:

  • A Cloud VPS or Root Server that is only connected to WAN

  • Distributed Systems using a central proxy (p.e. on-site at customers)

In this case we might need other tools like proxy-forwarder to act as forwarder:

> curl https://superstes.eu
# proxy-forwarder
2023-08-29 20:49:10 | INFO | handler | 192.168.11.104:36386 <=> superstes.eu:443/tcp | connection established
# squid
NONE_NONE/200 0 CONNECT superstes.eu:443 - HIER_NONE/- -
TCP_TUNNEL/200 6178 CONNECT superstes.eu:443 - HIER_DIRECT/superstes.eu -

> curl http://superstes.eu
# proxy-forwarder
2023-08-29 20:49:07 | INFO | handler | 192.168.11.104:50808 <=> superstes.eu:80/tcp | connection established
# squid
TCP_REFRESH_MODIFIED/301 477 GET http://superstes.eu/ - HIER_DIRECT/superstes.eu text/html

squid_remote


Troubleshooting

What does not work?

One might want to try some other ways of sending/redirecting traffic to a squid proxy.

Here are some examples that DO NOT WORK

  • Destination NAT to remote Squid server in transparent mode

    # journalctl -u squid.service -n 50
    ...
    WARNING: Forwarding loop detected for
    ...
    TCP_MISS/403 ORIGINAL_DST/<proxy-ip>
    ...
    
  • DNAT 80/443 to squid in non-transparent mode

    # journalctl -u squid.service -n 50
    ...
    Missing or incorrect access protocol
    ...
    NONE/400
    ...
    
  • IPTables/NFTables TPROXY to socat forwarder

    SOCat is actually correctly receiving and forwarding the traffic - BUT practically it acts like a DNAT operation

    # 'client'
    socat tcp-listen:3129,reuseaddr,fork,bind=127.0.0.1,ip-transparent tcp:<proxy-ip>:3129
    
    # journalctl -u squid.service -n 50
    ...
    WARNING: Forwarding loop detected for
    ...
    TCP_MISS/403 ORIGINAL_DST/<proxy-ip>
    ...
    
  • Intercept/TPROXY mode with Squid inside docker container

    Essentially docker seems to be NATing the traffic.

    ERROR: NF getsockopt(ORIGINAL_DST) failed on conn18 local=192.168.0.2:3130 remote=192.168.0.1:48910 FD 12 flags=33: (2) No such file or directory
    ERROR: NAT/TPROXY lookup failed to locate original IPs on conn18 local=192.168.0.2:3130 remote=192.168.0.1:48910 FD 12 flags=33
    

Known problems

  • Clients have many timeouts

    It may be that your cache size is too small.

    This can happen when many requests hit the proxy in a short time period.

    Possible Solution:

    • Increase your main cache:

      cache_mem 1024 MB (see docs - cache_mem)

    • Increase your session cache:

      sslproxy_session_cache_size 512 MB

    • Increase your ssl cache (only if you intercept ssl)

      ssl_db => sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/lib/squid/ssl_db -M 256M

    • Increase your ssl session timeout

      sslproxy_session_ttl 600

  • Bus error

    It seems this happens when the value of sslproxy_session_cache_size is larger than the one of ssl_db

  • NONE_NONE/409 & SECURITY ALERT: Host header forgery detected

    This error can occur whenever the squid proxy runs in intercept mode and resolves the target hostname to another IP than the client.

    That check can help against attacks that can trick the proxy into allowing bad traffic.

    As today’s DNS servers use very low TTLs it might happen that some traffic triggers this check as false-positive.

    You can disable this check for HTTP (plaintext) traffic by setting host_verify_strict off (default)

    HTTPS traffic will still be forced fail for some unclear reason.. :(

    See also: Squid wiki - host_verify_strict & Squid wiki - host header forgery

    You could - of course use the proxy-forwarder to translate the intercepted TCP traffic into HTTP & HTTPS requests that you are able to send to the ‘forward-proxy’ port of squid. (that one will ignore that check…)