CVE-2023-36812 (Java, published 2023-06-30T23:15Z)
Description: value: OpenTSDB is a open source, distributed, scalable Time Series Database (TSDB). OpenTSDB is vulnerable to Remote Code Execution vulnerability by writing user-controlled input to Gnuplot configuration file and running Gnuplot with the generated configuration. This issue has been patched in  commit `07c4641471c` and further refined in commit `fa88d3e4b`. These patches are available in the `2.4.2` release. Users are advised to upgrade. User unable to upgrade may disable Gunuplot via the config option`tsd.core.enable_ui = true` and remove the shell files `mygnuplot.bat` and `mygnuplot.sh`.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
Repository: https://github.com/OpenTSDB/opentsdb
Files changed:
src/tsd/GraphHandler.java
test/tsd/TestGraphHandler.java
Commit message: Improved fix for #2261.
Regular expressions wouldn't catch the newlines or possibly other
control characters. Now we'll use the TAG validation code to make
sure the inputs are only plain ASCII printables first.
Fixes CVE-2018-12972, CVE-2020-35476


Vulnerable code:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

    assertInvalidPlotParam("yrange", "out%20right%20top%0aset%20yrange%20[33:system(%20");
    assertInvalidPlotParam("style", "dots%20[33:system(%20");
    assertInvalidPlotParam("ylabel", "[33:system(%20");
    assertInvalidPlotParam("title", "[33:system(%20");
    assertInvalidPlotParam("y2label", "[33:system(%20");
    assertInvalidPlotParam("smooth", "[33:system(%20");
    assertInvalidPlotParam("yformat", "%252.[33:system");

Fixed code:
import net.opentsdb.core.*;
import net.opentsdb.core.Tags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

      validateString("wxh", wxh);
      validateString("yrange", value, "[:]");
      validateString("y2range", value, "[:]");
      validateString("ylabel", value, " ");
      validateString("y2label", value, " ");
      validateString("yformat", value, "% ");
      validateString("y2format", value, "% ");
      validateString("xformat", value, "% ");
      validateString("key", value);
      validateString("title", value, " ");
      validateString("bgcolor", value);
      validateString("fgcolor", value);
      validateString("smooth", value);
      validateString("style", value);
  static void validateString(final String what, final String s) {
    validateString(what, s, "");
  }

  public static void validateString(final String what, final String s, String specials) {
    if (s == null) {
      throw new BadRequestException("Invalid " + what + ": null");
    } else if ("".equals(s)) {
      throw new BadRequestException("Invalid " + what + ": empty string");
    }
    final int n = s.length();
    for (int i = 0; i < n; i++) {
      final char c = s.charAt(i);
      if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
          || ('0' <= c && c <= '9') || c == '-' || c == '_' || c == '.'
          || c == '/' || Character.isLetter(c) || specials.indexOf(c) != -1)) {
        throw new BadRequestException("Invalid " + what
            + " (\"" + s + "\"): illegal character: " + c);
      }
    }
  }

    assertInvalidPlotParam("y2range", "[42:%0a[33:system('touch /tmp/poc.txt')]");
    assertInvalidPlotParam("key", "out%20right%20top%0aset%20yrange%20[33:system(%20");
    assertInvalidPlotParam("key", "%3Bsystem%20%22cat%20/home/ubuntuvm/secret.txt%20%3E/tmp/secret.txt%22%20%22");
    assertInvalidPlotParam("style", "dots%20%0a[33:system(%20");
    assertInvalidPlotParam("style", "%3Bsystem%20%22cat%20/home/ubuntuvm/secret.txt%20%3E/tmp/secret.txt%22%20%22\"");
    assertInvalidPlotParam("ylabel", "system(%20no%0anewlines");
    assertInvalidPlotParam("title", "system(%20no%0anewlines");
    assertInvalidPlotParam("y2label", "system(%20no%0anewlines");
  }

  @Test
  public void setWXH() throws Exception {
    assertPlotDimension("wxh",  "720x640");
    assertInvalidPlotDimension("wxh", "720%0ax640");
    assertInvalidPlotParam("bgcolor", "xBDE%0AAD");
    assertInvalidPlotParam("fgcolor", "xBDE%0AAD");
    assertInvalidPlotParam("smooth", "bezier%20system(%20");
    assertInvalidPlotParam("smooth", "fnormal%0asystem(%20");
    assertInvalidPlotParam("yformat", "%252.system(%20");
    assertInvalidPlotParam("yformat", "%252.%0asystem(%20");
  private static void assertPlotDimension(String param, String value) {
    Plot plot = mock(Plot.class);
    HttpQuery query = mock(HttpQuery.class);
    when(query.getQueryStringParam(param)).thenReturn(value);
    GraphHandler.setPlotParams(query, plot);
  }

  private static void assertInvalidPlotDimension(String param, String value) {
    Plot plot = mock(Plot.class);
    HttpQuery query = mock(HttpQuery.class);
    when(query.getQueryStringParam(param)).thenReturn(value);
    try {
      GraphHandler.setPlotDimensions(query, plot);
      fail("Expected BadRequestException");
    } catch (BadRequestException e) { }
  }