Quick tips: FortiClient FQDN-based split tunneling with IPsec, FortiGate Web Application Firewall URL access

Dear reader, I have another installment of the quick tips series. This time the topics are FQDN-based split tunneling for IPsec with FortiClient and how to use the URL access feature of a FortiGate Web Application Firewall (WAF) profile.

Models and versions:

  • FortiGate 70G on 7.6.7
  • FortiClient EMS on 7.4.7
  • FortiClient on 7.4.6
  • Windows 11 client on 25H2

FortiClient FQDN-based split tunneling with IPsec

One of the nice things about SSL-VPN on a FortiGate is the fact that you can do FQDN-based split tunneling with relative ease by configuring it directly in a firewall policy as the destination and performing split tunneling based on the policy destination. With IPsec, this isn’t possible, because that’s not how the protocol works. The documentation even says that you cannot use FQDN objects in the split tunnel configuration.

Note:

FQDNs are not supported in split-tunnel destinations. If FQDNs have been configured in the split-tunnel address group, it cannot be applied in the (set ipv4-split-include) config of the dialup IPsec.

That won’t stop us, however.

In order to make this happen, we can use EMS’ application-based split tunneling feature while editing a tunnel. In there, you can include or exclude Cloud and Video Streaming Applications, but also Domains, and that last point is the one that is interesting, because you can use FQDNs here.

In this case, I am adding the FQDN of this blog, blog.guenay.at, to the configuration.

EMS VPN FQDN-based split tunneling configuration

The VPN configuration of a FortiGate doesn’t require any special configuration for this to work, and I have configured a regular PSK VPN with split tunneling, that routes 192.168.0.0/16 into the tunnel.

FortiGate VPN configuration

config vpn ipsec phase1-interface
    edit "PSK-VPN"
        set type dynamic
        set interface "wan1"
        set ike-version 2
        set keylife 28800
        set peertype any
        set net-device disable
        set mode-cfg enable
        set ipv4-dns-server1 192.168.1.169
        set proposal aes256gcm-prfsha512
        set dhgrp 31
        set eap enable
        set eap-identity send-request
        set authusrgrp "VPN_USERS"
        set client-resume enable
        set client-resume-interval 600
        set transport auto
        set ipv4-start-ip 172.16.101.1
        set ipv4-end-ip 172.16.101.20
        set ipv4-split-include "BASE-RFC1918-192"
        set psksecret ENC TaAvnptXQNmMUc/zjoy5F1CweIG8wOZUwliDjZCfvt8lr36rtJl1i95oOzgQjN22lPhP3tvZ0HOLy5XCQNtGvKO+piZ6I2wDKHVXe4Q787D5NNdrtPwonaDXZxrdyAZ5hl3r3am7zJMhWC5F5Pdz/ms5t5oew98CiSjzPUv/FHHYrxObTvi+IrxH+MW5VmUIa85ZEVlmMjY3dkVA
        set dpd-retryinterval 5
    next
end
config vpn ipsec phase2-interface
    edit "PSK-VPN"
        set phase1name "PSK-VPN"
        set proposal aes256gcm
        set dhgrp 31
        set keepalive enable
        set keylifeseconds 3600
    next
end

A policy to allow the WAN-bound traffic from the VPN is, of course, also needed for our FQDN split tunneling to work.

FortiGate VPN WAN firewall policy

Once the connection on FortiClient is up, we can see, in the log file of the VPN (in my case it is C:\Program Files\Fortinet\FortiClient\logs\trace\iked_ikev2_PSK-VPN7EB027B2_1.log), that split tunneling is active and per-application policies have been applied in include mode.

[2026-08-08 18:08:27.6368270 UTC+02:00] [18216:12516] [iked  801    info] Detect split tunnel with subnet 192.168.0.0 and mask 255.255.0.0
[2026-08-08 18:08:27.6368279 UTC+02:00] [18216:12516] [iked  806    info] split is 1
[2026-08-08 18:08:27.6368294 UTC+02:00] [18216:12516] [iked  365    info] Applying per-application policies
[2026-08-08 18:08:27.6368305 UTC+02:00] [18216:12516] [iked  366    info] tunnel_name = PSK-VPN
[2026-08-08 18:08:27.6373625 UTC+02:00] [18216:12516] [iked  2511    info] Non-multiconnect vpn, set dns priority to 1
[2026-08-08 18:08:27.6373679 UTC+02:00] [18216:12516] [iked  2531    info] dns_priority 1
[2026-08-08 18:08:27.6373693 UTC+02:00] [18216:12516] [iked  381    info] mode = 1, dp.priority = 1
[2026-08-08 18:08:27.6373706 UTC+02:00] [18216:12516] [iked  334    info] Applying per-application policies in include mode

For verification, here is the route table of the VPN-connected Windows client before doing anything else.

Route table before anything

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
          0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
     172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
      192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
    192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
    192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
    192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
  192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
        224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
        224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
  255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
===========================================================================
Persistent Routes:
  Network Address          Netmask  Gateway Address  Metric
          0.0.0.0          0.0.0.0     172.16.101.2  Default
===========================================================================

Once the client browses to the domain listed, the following lines appear in the log file:

[2026-08-08 18:04:47.2709530 UTC+02:00] [6764:16584] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(46.30.213.70), split_mask(255.255.255.255), local_gwy(192.168.1.228), remote_gwy(192.168.1.202)]
[2026-08-08 18:04:47.2735528 UTC+02:00] [6764:16584] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(46.30.213.70), split_mask(255.255.255.255), local_gwy(192.168.1.228), remote_gwy(192.168.1.202)]

The IP corresponds to the IP of blog.guenay.at.

PS C:\Users\adkevin.AD.000> ping blog.guenay.at

Pinging blog.guenay.at [46.30.213.70] with 32 bytes of data:

If we check the route table after this, we see that the IP has been added and points towards the VPN interface.

Route table after adding FQDN route

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
          0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
     46.30.213.70  255.255.255.255     172.16.101.2     172.16.101.1    100
        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
     172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
      192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
    192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
    192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
    192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
  192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
        224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
        224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
  255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
===========================================================================
Persistent Routes:
  Network Address          Netmask  Gateway Address  Metric
          0.0.0.0          0.0.0.0     172.16.101.2  Default
===========================================================================

The FortiGate also shows this traffic in the Forward Traffic log.

FortiGate VPN traffic log

If we disconnect the VPN, the restoration and removal of the routes can be seen. The domain routes aren’t specifically mentioned here, however.

[2026-08-08 18:14:11.9604854 UTC+02:00] [13592:13376] [iked  1285    info] Restore route and interface setting...
[2026-08-08 18:14:11.9626475 UTC+02:00] [13592:13376] [iked  1303    info] Restore routes...
[2026-08-08 18:14:11.9627101 UTC+02:00] [13592:13376] [iked  364    info] Route del(0): Destination=192.168.1.202 Prefix=32 Nexthop=0.0.0.0 Index=3
[2026-08-08 18:14:11.9627259 UTC+02:00] [13592:13376] [iked  364    info] Route del(0): Destination=192.168.0.0 Prefix=16 Nexthop=172.16.101.2 Index=59
[2026-08-08 18:14:11.9627269 UTC+02:00] [13592:13376] [iked  1318    info] Restore interface default route option...
[2026-08-08 18:14:11.9627275 UTC+02:00] [13592:13376] [iked  1330    info] Restore interface metric...
[2026-08-08 18:14:11.9633352 UTC+02:00] [13592:13376] [iked  1345    info] Remove route and interface restore proto file(0) The operation completed successfully....

For fun, let’s see what happens if I use the Video Streaming Applications option and add YouTube and browse to just the homepage.

Adding YouTube routes and route table

[2026-08-08 19:15:05.6023606 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.150.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6026407 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.151.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6028006 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.157.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6032742 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.156.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6034173 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.152.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6035045 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.155.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6035589 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.154.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6035967 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.153.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6036401 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.150.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6036984 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.151.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6037528 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.157.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6053667 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.156.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6054232 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.152.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6054551 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.155.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6054903 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.154.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.6055338 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.153.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8516774 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.127.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8518532 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.110.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8519067 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.250.154.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8519922 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.20.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8520852 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(192.178.183.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8521251 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.13.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8521565 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.14.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8521861 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.127.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8522174 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.110.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8522468 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.250.154.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8522766 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.20.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8523062 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(192.178.183.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8523485 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.13.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
[2026-08-08 19:15:05.8523786 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.14.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
          0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  142.250.154.119  255.255.255.255     172.16.101.2     172.16.101.1    100
   142.251.13.119  255.255.255.255     172.16.101.2     172.16.101.1    100
   142.251.14.119  255.255.255.255     172.16.101.2     172.16.101.1    100
   142.251.20.119  255.255.255.255     172.16.101.2     172.16.101.1    100
  142.251.110.119  255.255.255.255     172.16.101.2     172.16.101.1    100
  142.251.127.119  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.150.4  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.151.4  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.152.4  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.153.4  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.154.4  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.155.4  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.156.4  255.255.255.255     172.16.101.2     172.16.101.1    100
    142.251.157.4  255.255.255.255     172.16.101.2     172.16.101.1    100
     172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
      192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
    192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
    192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
    192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
  192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
  192.178.183.119  255.255.255.255     172.16.101.2     172.16.101.1    100
        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
        224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
        224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
  255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
===========================================================================
Persistent Routes:
  Network Address          Netmask  Gateway Address  Metric
          0.0.0.0          0.0.0.0     172.16.101.2  Default
===========================================================================

Lots of routes are being added, and this is reflected in the route table.

If you use the exclude mode in the EMS VPN configuration, you see this in the log, and the routes are added to the routing table using the regular interface with your normal default gateway. Funnily enough, you don’t see these routes being added in the log.

[2026-08-08 18:12:49.3312644 UTC+02:00] [13592:13376] [iked  801    info] Detect split tunnel with subnet 192.168.0.0 and mask 255.255.0.0
[2026-08-08 18:12:49.3312680 UTC+02:00] [13592:13376] [iked  806    info] split is 1
[2026-08-08 18:12:49.3312696 UTC+02:00] [13592:13376] [iked  365    info] Applying per-application policies
[2026-08-08 18:12:49.3312705 UTC+02:00] [13592:13376] [iked  366    info] tunnel_name = PSK-VPN
[2026-08-08 18:12:49.3327815 UTC+02:00] [13592:13376] [iked  2511    info] Non-multiconnect vpn, set dns priority to 1
[2026-08-08 18:12:49.3327865 UTC+02:00] [13592:13376] [iked  2531    info] dns_priority 1
[2026-08-08 18:12:49.3327878 UTC+02:00] [13592:13376] [iked  381    info] mode = 2, dp.priority = 1
[2026-08-08 18:12:49.3327889 UTC+02:00] [13592:13376] [iked  287    info] Applying per-application policies in exclude mode

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
          0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  142.250.154.119  255.255.255.255  192.168.100.100    192.168.100.2    124
   142.251.13.119  255.255.255.255  192.168.100.100    192.168.100.2    124
   142.251.14.119  255.255.255.255  192.168.100.100    192.168.100.2    124
   142.251.20.119  255.255.255.255  192.168.100.100    192.168.100.2    124
  142.251.110.119  255.255.255.255  192.168.100.100    192.168.100.2    124
  142.251.127.119  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.150.4  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.151.4  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.152.4  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.153.4  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.154.4  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.155.4  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.156.4  255.255.255.255  192.168.100.100    192.168.100.2    124
    142.251.157.4  255.255.255.255  192.168.100.100    192.168.100.2    124
     172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
      192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
    192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
    192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
    192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
  192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
  192.178.183.119  255.255.255.255  192.168.100.100    192.168.100.2    124
        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
        224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
        224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
  255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
===========================================================================
Persistent Routes:
  Network Address          Netmask  Gateway Address  Metric
          0.0.0.0          0.0.0.0     172.16.101.2  Default
===========================================================================

This method of FQDN-based split tunneling is a nice compromise when transitioning from SSL-VPN to IPsec. Don’t forget the Cloud Applications option in this, because it offers a few often-used services and acts like the other options.

FortiGate Web Application Firewall URL access

Due to a blog post about FortiClient EMS Let’s Encrypt Security that I wrote for my employer, I was told about the URL access feature that lives inside the WAF profile of a FortiGate, which I wasn’t aware of before. Outside of the CLI reference, I cannot find any documentation of this feature for a FortiGate, only for FortiWeb, but it is relatively self-explanatory. Still, let’s see how to configure it.

Note: The URL access feature is CLI-only, but you should still enable Web Application Firewall in Feature Visibility, because otherwise the option to assign the profile in a policy does not show up in the GUI. Also, you can do much the same with a web filter profile, which I do in the post above, so the WAF option is not the only method to achieve this; it just saves on another profile.

On the CLI, you can configure a basic URL access block as follows:

config waf profile
    edit "WAF_URL-ACCESS"
        config url-access
            edit 1
                set address "ubuntu-ws-1.ad.labdomain.com"
                set action block
                set log enable
                config access-pattern
                    edit 1
                        set srcaddr "all"
                        set pattern ".*/login.html$"
                        set regex enable
                    next
                end
            next
        end
    next
end

Let’s go through it.

The address option determines the backend host that is being protected, and it is an actual address object. I have tested it with the types ipmask and fqdn, and both worked here, assuming your DNS resolution is correct for the FQDN option.

action has the block option, which does exactly that, bypass, which allows the connection and skips all other WAF scanning, and permit, which allows the connection and continues with WAF scanning.

log is for logging; who would have thought.

In the access-pattern entry itself, we can set the srcaddr option, which determines to which source address this should apply, so we can target only specific addresses. Again, this is an actual address object.

Enabling regex does that, and because I prefer regex-matching over everything else, I have it enabled. Disabling it probably does a simple match on the URL, but I didn’t test this because if regex is available, I will use only that.

The actual regex pattern is used in the pattern option. In my case, I am checking for every URL path that ends in /login.html.

That’s it on how to configure the feature.

Note that there is no implicit deny rule here, so if you don’t have an entry for a pattern, it is allowed. Entries are matched top down, so keep that in mind.

Assign the WAF profile in a policy, and if you perform deep inspection, either with an SSL/SSH profile or a virtual server, you can also control access in HTTPS traffic.

FortiGate WAF firewall policy

If I test the URL https://ubuntu-ws-1.ad.labdomain.com/login.html on my client, I get the block, as expected, and I see this in my FortiGate Web Application Firewall log.

Client WAF URL access block
WAF URL access block log

Browsing to any other URL, like https://ubuntu-ws-1.ad.labdomain.com/index.html or just https://ubuntu-ws-1.ad.labdomain.com, does not lead to a block, because I never explicitly mentioned this in my WAF profile and I also don’t have a catch-all entry.

If you ever need to debug this feature, then you need to know that the WAF feature uses the wad process, and the category http is the important one.

diagnose wad debug enable category http
diagnose wad debug enable level info
diagnose debug enable

In the debugs, you can see the request coming in and the URL access being matched.

FortiGate Web Application Firewall debug

[I][p:1758][s:11239][r:33554552] wad_dump_http_request             :3134  hreq=0x7f9df0d8e0 Received request from client: 192.168.100.2:56531

GET /login.html HTTP/1.1
Host: ubuntu-ws-1.ad.labdomain.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: "Not=A?Brand";v="99", "Microsoft Edge";v="151", "Chromium";v="151"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36 Edg/151.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-GB,en;q=0.9,en-US;q=0.8
Cookie: session=expiry=1786211939675194

[I][p:1758][s:11239][r:33554552] wad_http_parse_host               :2019  host=[28]ubuntu-ws-1.ad.labdomain.com
[I][p:1758][s:11239][r:33554552] wad_http_str_canonicalize         :2621  enc=0 path=/login.html len=11 changes=0
[I][p:1758][s:11239][r:33554552] wad_http_req_detect_special       :14589 captive_portal detected: false, preflight=(null)
[I][p:1758][s:11239][r:33554552] wad_http_req_domain_front_chk     :9751  chk uri host res=pass (is_abs=0 http_encap=1 connect=0): ubuntu-ws-1.ad.labdomain.com
[I][p:1758][s:11239][r:33554552] wad_http_waf_access_control       :1242
[I][p:1758][s:11239][r:33554552] wad_http_waf_access_control       :1269  WAF matched URL access action=1
[I][p:1758][s:11239][r:33554552] __wad_http_build_replmsg_resp     :933   Generating replacement message. repmsg_id 77
[I][p:1758][s:11239][r:33554552] wad_dump_fwd_http_resp            :3155  hreq=0x7f9df0d8e0 Forward response from Internal:

HTTP/1.1 403 Forbidden
Connection: close
Content-Type: text/html
Cache-Control: no-cache
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: frame-ancestors 'self'
Content-Length: 35039

If you want a configuration that allows a specific URL path and blocks all others, you can configure it like this:

config waf profile
    edit "WAF_URL-ACCESS"
        config url-access
            edit 1
                set address "ubuntu-ws-1.ad.labdomain.com"
                set log enable
                config access-pattern
                    edit 1
                        set srcaddr "all"
                        set pattern ".*/login.html$"
                        set regex enable
                    next
                end
            next
            edit 2
                set address "ubuntu-ws-1.ad.labdomain.com"
                set action block
                set log enable
                config access-pattern
                    edit 1
                        set srcaddr "all"
                        set pattern ".*"
                        set regex enable
                    next
                end
            next
        end
    next
end

And that’s that feature explained.

Short and sweet and done

The configuration for both things in this quick tips installment isn’t anything to write home about, as it should be for this series, but I haven’t seen much on either of these things, so hopefully this helps at least you, dear reader.

Comments

One response to “Quick tips: FortiClient FQDN-based split tunneling with IPsec, FortiGate Web Application Firewall URL access”

  1. James Mitchell avatar
    James Mitchell

    Simple, effective, and easy to understand.

Be civil. Stay on topic. Don’t lie.

Leave a Reply

Your email address will not be published. Required fields are marked *