Posts FortiGate VPN-SSL Honeypot

   

Post
Cancel

FortiGate VPN-SSL Honeypot

FortiGate VPN-SSL Honeypot

The following blog post contains an example of running and configuring the FortiGate VPN-SSL Honeypot project. This project can be found in Github here:

Description and Capabilities

A deception honeypot that mimics FortiGate VPN-SSL devices to trap brute force attempts, detect deliberately exfiltrated credentials for counter‑intelligence, and report malicious activity to external intelligence feeds (VT, OTX).

  • Python & Flask for login portal.
  • Nginx fronts the portal with TLS.
  • Docker environment via docker compose.
  • SQLite stores raw telemetry (creds & symlink‑exploit probes).
  • A suite of helper scripts automate parsing, reporting, and alerting.

The Honeypot has the following capabilities currently implemented.

🚩FeatureDescription
📧Report to emailHTML dashboard with summary of Honeypot traps
🌐Report to OTXPosts new bad IPs to AlienVault OTX pulses
🔍Report to VTDown‑votes & comments IPs on VirusTotal
🙈Counter‑intelFlags any password present in exfiltrated_passwords.txt used for counter ingelligence. Deliberately exfiltrate credentials and detect attempts to use them.
⚠️Symlink exploit detectionCatches symlink exploit attempts. Unfortunately, as this is not public yet it has been removed. Showing the detection approach would shows also how to exploit it.

The following sections will show an example of running the Honeypot in a cloud server among its capabilities.

Installation and Running the Honey

The Honeypot is written in Python and built around docker-compose.

So, in order to start running the Honeypot ensure you have installed Docker. After that, it can be run running these commands.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. Clone & build
$ git clone [https://github.com/PeterGabaldon/Fortigate.VPN-SSL.Honeypot.git](https://github.com/PeterGabaldon/Fortigate.VPN-SSL.Honeypot.git)
$ cd Fortigate.VPN-SSL.Honeypot-main

# 2. Generate TLS material (one‑off)
$ ./nginx/dist/conf/ssl/gen-cert.sh
$ ./nginx/dist/conf/ssl/gen-dhparam.sh 2048

# 3. Run with docker compose
$ docker compose up # 🔥 boots nginx & honeypot

# 4. Parse logs & load SQLite
$ ./parse.sh   # ➜ data/db/honeypot.db gets populated

Before running the Honeypot, it is necessary to generate the DH params and certificate configured in nginx.

If you want to modify some parameters about the certificate, edit ./nginx/dist/conf/ssl/gen-cert.sh. By default the certificate generated will use the following information:

1
2
3
4
$ cat ./nginx/dist/conf/ssl/gen-cert.sh
#! /bin/bash

/usr/bin/openssl req -subj '/C=US/ST=California/L=Sunnyvale/O=Fortinet/OU=FortiGate/CN=FGT61FTZ24061580/[email protected]/' -nodes -x509 -sha512 -newkey rsa:8192 -keyout "nginx.key" -out "nginx.crt" -days 3650

After that, run docker compose up and the Honeypot will be up and reachable from port 10443. Modify nginx/dist/conf/honey.conf if want to run it in another port.

1
2
3
4
5
6
7
8
9
10
11
12
$ cat nginx/dist/conf/honey.conf
############################################
### NGINX T-Pot configuration file by mo ###
############################################

server {

    #########################
    ### Basic server settings
    #########################
    listen 10443 ssl http2;
    [...]

After that, the Honey will be accessible.

Just for your information, once it is published to internet, in a few days it will appea in internet service mappers like Shodan or Censys.

Parsing

Honeypot data is logged at data/log/honey/creds.log and nginx data is logged at data/log/nginx/access.log.

Here is an example of information captured by the Honey, with the credentials redacted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
trainee T[***]4 185.213.193.234 2025-07-31T15:00:18.385476+00:00
sromero s[***]o 91.92.34.11 2025-07-31T15:00:44.028099+00:00
ke 1[***]8 167.160.161.193 2025-07-31T15:00:49.098469+00:00
he P[***]d 167.160.161.216 2025-07-31T15:01:02.217613+00:00
fausto F[***]3 23.234.70.149 2025-07-31T15:01:05.474080+00:00
nkennedy n[***]y 107.174.46.11 2025-07-31T15:01:19.092927+00:00
ke 1[***]8 167.160.161.193 2025-07-31T15:01:22.168543+00:00
isreal i[***]2 43.225.189.151 2025-07-31T15:01:33.667071+00:00
he P[***]d 167.160.161.216 2025-07-31T15:01:35.845423+00:00
amy W[***]1 23.234.117.79 2025-07-31T15:01:44.388001+00:00
sqluser s[***]1 43.225.189.170 2025-07-31T15:02:53.167762+00:00
intranet p[***]d 23.234.68.230 2025-07-31T15:02:55.229870+00:00
pferguson p[***]n 104.168.80.13 2025-07-31T15:03:05.072113+00:00
report %[***]x 68.235.46.88 2025-07-31T15:04:32.869353+00:00
uking u[***]g 94.26.88.5 2025-07-31T15:04:45.977357+00:00
ladams l[***]s 198.23.161.10 2025-07-31T15:04:46.027902+00:00
teresa T[***]1 23.234.114.197 2025-07-31T15:04:50.948803+00:00
tstephens t[***]s 94.26.88.3 2025-07-31T15:05:36.710669+00:00
kf 1[***]8 167.160.161.193 2025-07-31T15:06:09.337948+00:00
jolson j[***]n 198.23.161.3 2025-07-31T15:06:14.681890+00:00
hf P[***]d 167.160.161.216 2025-07-31T15:06:36.153136+00:00
kf 1[***]8 167.160.161.193 2025-07-31T15:06:42.417378+00:00
aaron a[***]1 149.40.50.116 2025-07-31T15:06:47.697336+00:00
hf P[***]d 167.160.161.216 2025-07-31T15:07:09.860109+00:00
wpierce w[***]e 94.26.105.3 2025-07-31T15:07:32.213667+00:00
elong e[***]g 192.109.138.9 2025-07-31T15:08:21.726254+00:00
ybryant y[***]t 94.26.105.8 2025-07-31T15:08:26.972756+00:00
oromero o[***]o 104.168.80.3 2025-07-31T15:08:45.380433+00:00
isreal i[***]2 43.225.189.151 2025-07-31T15:08:47.140009+00:00
scans P[***]0 68.235.46.145 2025-07-31T15:09:05.908442+00:00
simon s[***]n 45.134.142.222 2025-07-31T15:10:19.605062+00:00
oallen o[***]n 104.168.80.9 2025-07-31T15:10:20.994895+00:00
sqluser s[***]1 43.225.189.170 2025-07-31T15:10:34.016648+00:00
landon l[***]3 146.70.165.70 2025-07-31T15:11:13.777620+00:00
kg 1[***]8 167.160.161.193 2025-07-31T15:11:35.171434+00:00

The Honeypot contains a parse script that parses the information and stores it in an sqlite database. Also, the results of the parsing is showed by the output of the script. It will also produce a JSON with the results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
$ bash parse.sh
Number of username/password test by IP
-----------------------
1       94.26.88.5
1       94.26.88.3
1       94.26.105.8
1       94.26.105.3
1       94.26.105.11
1       91.92.34.11
1       68.235.46.88
1       68.235.46.145
1       45.134.142.222
2       43.225.189.170
2       43.225.189.151
1       23.94.58.7
1       23.234.70.149
1       23.234.69.59
1       23.234.68.230
1       23.234.117.79
1       23.234.114.197
1       23.162.40.165
1       198.23.161.3
1       198.23.161.10
1       192.109.138.9
1       185.213.193.234
6       167.160.161.216
6       167.160.161.193
1       149.40.50.116
1       146.70.165.70
1       107.174.46.11
1       104.168.80.9
1       104.168.80.3
1       104.168.80.13

Number of username/password test by username,password,IP
-----------------------
1	zramirez,z[***]z,94.26.105.11
1	ybryant,y[***]t,94.26.105.8
1	wpierce,w[***]e,94.26.105.3
1	uking,u[***]g,94.26.88.5
1	tstephens,t[***]s,94.26.88.3
1	trainee,T[***]4,185.213.193.234
1	teresa,T[***]1,23.234.114.197
1	sromero,s[***]o,91.92.34.11
2	sqluser,s[***]1,43.225.189.170
1	simon,s[***]n,45.134.142.222
1	scans,P[***]0,68.235.46.145
1	report,%[***]x,68.235.46.88
1	pferguson,p[***]n,104.168.80.13
1	oromero,o[***]o,104.168.80.3
1	oallen,o[***]n,104.168.80.9
1	nkennedy,n[***]y,107.174.46.11
1	media,s[***]t,23.162.40.165
1	landon,l[***]3,146.70.165.70
1	ladams,l[***]s,198.23.161.10
2	kg,1[***]8,167.160.161.193
2	kf,1[***]8,167.160.161.193
2	ke,1[***]8,167.160.161.193
1	jolson,j[***]n,198.23.161.3
2	isreal,i[***]2,43.225.189.151
1	intranet,p[***]d,23.234.68.230
2	hg,P[***]d,167.160.161.216
2	hf,P[***]d,167.160.161.216
2	he,P[***]d,167.160.161.216
1	frussell,f[***]l,23.94.58.7
1	fausto,F[***]3,23.234.70.149
1	elong,e[***]g,192.109.138.9
1	andrew,P[***]3,23.234.69.59
1	amy,W[***]1,23.234.117.79
1	aaron,a[***]1,149.40.50.116


Number of times each username was seen
-----------------------
1       zramirez
1       ybryant
1       wpierce
1       uking
1       tstephens
1       trainee
1       teresa
1       sromero
2       sqluser
1       simon
1       scans
1       report
1       pferguson
1       oromero
1       oallen
1       nkennedy
1       media
1       landon
1       ladams
2       kg
2       kf
2       ke
1       jolson
2       isreal
1       intranet
2       hg
2       hf
2       he
1       frussell
1       fausto
1       elong
1       andrew
1       amy
1       aaron

Number of times each password was seen
-----------------------
1       z[***]z
1       y[***]t
1       w[***]e
1       W[***]1         
1       u[***]g
1       t[***]s
1       T[***]4         
1       T[***]1         
1       s[***]t         
1       s[***]o
2       s[***]1         
1       s[***]n
1       p[***]n         
1       P[***]0         
1       p[***]d         
1       P[***]3         
6       P[***]d         
1       o[***]o         
1       o[***]n         
1       n[***]y         
1       l[***]3         
1       l[***]s         
1       j[***]n         
2       i[***]2         
1       f[***]l         
1       F[***]3         
1       e[***]g         
1       P[***]3         
1       W[***]1         
1       a[***]1         
1       %[***]x         
6       1[***]8         

Dumping IPs to bad_ips.txt
-----------------------
104.168.80.13   2025-07-31T15:03:05.072113+00:00
104.168.80.3    2025-07-31T15:08:45.380433+00:00
104.168.80.9    2025-07-31T15:10:20.994895+00:00
107.174.46.11   2025-07-31T15:01:19.092927+00:00
146.70.165.70   2025-07-31T15:11:13.777620+00:00
149.40.50.116   2025-07-31T15:06:47.697336+00:00
167.160.161.193 2025-07-31T15:00:49.098469+00:00
167.160.161.193 2025-07-31T15:01:22.168543+00:00
167.160.161.193 2025-07-31T15:06:09.337948+00:00
167.160.161.193 2025-07-31T15:06:42.417378+00:00
167.160.161.193 2025-07-31T15:11:35.171434+00:00
167.160.161.193 2025-07-31T15:12:07.689597+00:00
167.160.161.216 2025-07-31T15:01:02.217613+00:00
167.160.161.216 2025-07-31T15:01:35.845423+00:00
167.160.161.216 2025-07-31T15:06:36.153136+00:00
167.160.161.216 2025-07-31T15:07:09.860109+00:00
167.160.161.216 2025-07-31T15:12:11.541550+00:00
167.160.161.216 2025-07-31T15:12:48.902528+00:00
185.213.193.234 2025-07-31T15:00:18.385476+00:00
192.109.138.9   2025-07-31T15:08:21.726254+00:00
198.23.161.10   2025-07-31T15:04:46.027902+00:00
198.23.161.3    2025-07-31T15:06:14.681890+00:00
23.162.40.165   2025-07-31T15:12:14.744867+00:00
23.234.114.197  2025-07-31T15:04:50.948803+00:00
23.234.117.79   2025-07-31T15:01:44.388001+00:00
23.234.68.230   2025-07-31T15:02:55.229870+00:00
23.234.69.59    2025-07-31T15:14:36.722044+00:00
23.234.70.149   2025-07-31T15:01:05.474080+00:00
23.94.58.7      2025-07-31T15:13:23.427372+00:00
43.225.189.151  2025-07-31T15:01:33.667071+00:00
43.225.189.151  2025-07-31T15:08:47.140009+00:00
43.225.189.170  2025-07-31T15:02:53.167762+00:00
43.225.189.170  2025-07-31T15:10:34.016648+00:00
45.134.142.222  2025-07-31T15:10:19.605062+00:00
68.235.46.145   2025-07-31T15:09:05.908442+00:00
68.235.46.88    2025-07-31T15:04:32.869353+00:00
91.92.34.11     2025-07-31T15:00:44.028099+00:00
94.26.105.11    2025-07-31T15:14:45.742802+00:00
94.26.105.3     2025-07-31T15:07:32.213667+00:00
94.26.105.8     2025-07-31T15:08:26.972756+00:00
94.26.88.3      2025-07-31T15:05:36.710669+00:00
94.26.88.5      2025-07-31T15:04:45.977357+00:00

Dumping IPs exploiting Symlink Persistence Method to bad_ips_symlink.txt
-----------------------

✅ JSON report written to /home/peter/Fortigate.VPN-SSL.Honeypot/output_parsing/report.json
🆗 SQLite schema ready (/home/peter/Fortigate.VPN-SSL.Honeypot/db/honeypot.db)
✅ Imported creds.log → honeypot_creds
✅ Imported nginx symlink attempts → symlink_exploits
🗑️  Logs truncated – parse.sh complete.

Scheduled Reporting

I recommend you to configure a service and a timer in systemd to periodically parse the information gathered at the Honeypot. The repository contains a template for it. Here is the one that I am using.

1
2
3
4
5
6
7
8
9
10
11
# cat fgate-vpn-honeypot-parse-and-report.service
# /etc/systemd/system/fgate-vpn-honeypot-parse-and-report.service
[Unit]
Description=Parse and report FortiGate VPN-SSL Honeypot service

[Service]
User=peter
Group=peter
WorkingDirectory=/home/peter/Fortigate.VPN-SSL.Honeypot/
Type=oneshot
ExecStart=/home/peter/Fortigate.VPN-SSL.Honeypot/parse.sh
1
2
3
4
5
6
7
8
9
10
11
# cat fgate-vpn-honeypot-parse-and-report.timer
# /etc/systemd/system/fgate-vpn-honeypot-parse-and-report.timer
[Unit]
Description=Parse and report FortiGate VPN-SSL Honeypot service timer

[Timer]
OnCalendar=*:0/30
Persistent=true

[Install]
WantedBy=timers.target

One important reminder, as the Honey run as root inside the container.

ACL Fix: the logs are written as root (inside the container). Allow the service user write access in order to the parser script (parse.sh) to be able to clear the logs:

1
$ sudo setfacl -m u:fortihoney:rw data/log/honey/creds.log data/log/nginx/access.log

Reporting Modules

The Honeypot contains some modules to perform reporting of the information gathered.

Every reporting module is at is report_to_ directory. I recommend you to run each one in a in its proper venv.

1
2
3
4
$ cd report_to_...
$ python -m venv .venv
$ . .venv/bin/activate
$ pip install -r requirements.txt

Reporting to VT/OTX

When reporting to VT/OTX, the datetime of the last successful report is saved to prevent reporting again the same IP addresses.

Configure the yaml file with the credentials and the comment and tag to use when reporting it in VT.

1
2
3
4
5
6
$ cat report_to_vt/vt_config/report_to_vt_bad_ips.yaml.template
vt_api_key: "<YOUR VT API KEY>"
tag: "FortiGate VPN‑SSL Honeypot"        # used for per‑tag state tracking
comment: "IP {ip} was seen bruteforcing FortiGate VPN-SSL at {seen} 🛡️"
honeypot:
  ip_file: "../honeypot_bad_ips.txt"     

Here is an example of a malicious IP being reported.

And the following is the configuration yaml file for reporting to OTX.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat report_to_otx/otx_config/report_to_otx.config.yaml.template
# config.yaml

otx_api_key: YOUR_OTX_API_KEY_HERE

pulse:
  name: "FortiGate VPN-SSL Honeypot"
  description: "IPs bruteforcing FortiGate VPN-SSL gathered from Honeypot"
  type: "blacklist"     # or "threat", "vulnerability", etc.
  public: true
  tlp: "WHITE"          # TLP: WHITE, GREEN, AMBER, RED

honeypot:
  ip_file: "./honeypot_bad_ips.txt"

And here you can find an example of the Pulse.

I also recommend to run the Reporting modules periodically via the systemd timer and service. Here is my full systemd service run by the timer every 30 minutes which, apart for the aforementioned periodic parsing, will run (in its respective venvs) the parsing scripts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# cat fgate-vpn-honeypot-parse-and-report.service
# /etc/systemd/system/fgate-vpn-honeypot-parse-and-report.service
[Unit]
Description=Parse and report FortiGate VPN-SSL Honeypot service

[Service]
User=peter
Group=peter
WorkingDirectory=/home/peter/Fortigate.VPN-SSL.Honeypot/
Type=oneshot
ExecStart=/home/peter/Fortigate.VPN-SSL.Honeypot/parse.sh
ExecStart=-/home/peter/Fortigate.VPN-SSL.Honeypot/report_to_otx/.venv/bin/python3 report_to_otx/report_to_otx.py -c report_to_otx/otx_config/report_to_otx.config.yaml
ExecStart=-/home/peter/Fortigate.VPN-SSL.Honeypot/report_to_otx/.venv/bin/python3 report_to_otx/report_to_otx.py -c report_to_otx/otx_config/report_to_otx.config.symlink.yaml
ExecStart=-/home/peter/Fortigate.VPN-SSL.Honeypot/report_to_vt/.venv/bin/python3 report_to_vt/report_to_vt.py -c report_to_vt/vt_config/report_to_vt_bad_ips.yaml
ExecStart=-/home/peter/Fortigate.VPN-SSL.Honeypot/report_to_vt/.venv/bin/python3 report_to_vt/report_to_vt.py -c report_to_vt/vt_config/report_to_vt_bad_ips.symlink.yaml

Reporting to Email

When reporting to email, the threshold of data to include in the report is selected by the –hours parameter, by default the last 24h of Honeypot data is used.

The configuration for reporting to email is shown below.

1
2
3
4
5
6
7
8
9
10
11
12
$ cat report_to_email/email_config.yaml.template
subject: "📊 FortiGate SSL‑VPN Honeypot – Daily Report"
from: "Honeypot <[email protected]>"   # optional; defaults to username if omitted
to:
  - [email protected]
  - [email protected]
smtp:
  host: smtp.example.com
  port: 465              # 465 = SMTPS, 587 = STARTTLS, 25 = opportunistic
  username: [email protected]
  password: "s3cr3tP@ssw0rd"
  use_ssl: true          # true ⇒ implicit TLS (465); false ⇒ STARTTLS (587/25)

It will produce an email with the following information.

  • 🌐 Attempts by IP
  • 🧑‍💻 Attempts by User / Pass / IP
  • 👤 Attempts by User
  • 🔑 Attempts by Password
  • ⚠️ Symlink Exploit Attempts
  • ⛔ Bad IPs
  • 💥 Exfiltrated Credentials

Counter-Intelligence (Deliberately Exfiltrate Credentials)

The honeypot isn’t just for telemetry—it actively hunts for leaked or deliberately planted passwords you care about.

  1. Watch-list file
    Drop one password per line into exfiltrated_passwords.txt at the repo root.
    Example:

    1
    2
    
     S3cr3tP@ssw0rd
     th1sIsB41t
    

The email‐report script loads that watch-list and flags any match during the selected time-window.

A dedicated table “💥 Exfiltrated Credentials” appears in the email report, showing the IP addresses along with the timestamp that used the credentials that were deliberately exfiltrated.

Ideas to exfiltrate credentials

  • Run stealer in a controlled environ
  • Write the credentials in pastebin
  • Sell the credentials in a forum

For example, I exfiltrated fake credentials in a Pastebin post and some days after an attempt was performed using them.

Symlink Exploit Detections

The Honeypot also detects attempt to use the Symlink persistence method in FortiGate units (https://www.fortinet.com/blog/psirt-blogs/analysis-of-threat-actor-activity)..)

Unfortunately, as this is not public yet it has been removed. Showing the detection approach would shows also how to exploit it.

For example, https://www.onyphe.io/ scans the internet finding vulnerable appliances and they were caught by the Honeypot. I am sorry but the detection of these attempts has is not available in the public version of the Honeypot. Maybe some day.

This post is licensed under CC BY 4.0 by the author.