CVE-2023-5122 (Go)

Grafana is an open-source platform for monitoring and observability. The CSV datasource plugin is a Grafana Labs maintained plugin for Grafana that allows for retrieving and processing CSV data from a remote endpoint configured by an administrator. If this plugin was configured to send requests to a bare host with no path (e.g.  https://www.example.com/ https://www.example.com/` ), requests to an endpoint other than the one configured by the administrator could be triggered by a specially crafted request from any user, resulting in an SSRF vector. AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator

Weakness:
CWE-918: name: Server-Side Request Forgery (SSRF)
description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

Repository: https://github.com/grafana/grafana-csv-datasource

Vulnerable code:
file_path: pkg/http_storage.go
snippet: func newRequestFromQuery(settings *backend.DataSourceInstanceSettings, customSettings dataSourceSettings, query dataSourceQuery) (*http.Request, error) {
	u, err := url.Parse(settings.URL + query.Path)
	if err != nil {
		return nil, err
	}

	params := make(url.Values)
	for _, p := range query.Params {
		params.Set(p[0], p[1])
	}

	// Query params set by admin overrides params set by query editor.
	values, err := url.ParseQuery(customSettings.QueryParams)
	if err != nil {
		return nil, err
	}
	for k, v := range values {
		params[k] = v
	}

	u.RawQuery = params.Encode()

	var method string
	if query.Method != "" {
		method = query.Method
	} else {
		method = "GET"
	}

	req, err := http.NewRequest(method, u.String(), strings.NewReader(query.Body))
	if err != nil {
		return nil, err
	}

	for _, p := range query.Headers {
		req.Header.Set(p[0], p[1])
	}

	return req, nil
}

Fixed code:
file_path: pkg/http_storage.go
snippet: func newRequestFromQuery(settings *backend.DataSourceInstanceSettings, customSettings dataSourceSettings, query dataSourceQuery) (*http.Request, error) {
	u, err := url.Parse(settings.URL + query.Path)
	if err != nil {
		return nil, err
	}

	// we need to verify that `query.Path` did not modify the hostname by doing tricks
	settingsURL, err := url.Parse(settings.URL)
	if err != nil {
		return nil, fmt.Errorf("invalid URL")
	}

	if settingsURL.Host != u.Host {
		// the host got changed by adding the path to it. this must not happen.
		return nil, fmt.Errorf("invalid URL + path combination")
	}

	params := make(url.Values)
	for _, p := range query.Params {
		params.Set(p[0], p[1])
	}

	// Query params set by admin overrides params set by query editor.
	values, err := url.ParseQuery(customSettings.QueryParams)
	if err != nil {
		return nil, err
	}
	for k, v := range values {
		params[k] = v
	}

	u.RawQuery = params.Encode()

	var method string
	if query.Method != "" {
		method = query.Method
	} else {
		method = "GET"
	}

	req, err := http.NewRequest(method, u.String(), strings.NewReader(query.Body))
	if err != nil {
		return nil, err
	}

	for _, p := range query.Headers {
		req.Header.Set(p[0], p[1])
	}

	return req, nil
}