CVE-2023-44384 (Ruby, published 2023-10-06T18:15Z)
Description: value: Discourse-jira is a Discourse plugin allows Jira projects, issue types, fields and field options will be synced automatically. An administrator user can make an SSRF attack by setting the Jira URL to an arbitrary location and enabling the `discourse_jira_verbose_log` site setting. A moderator user could manipulate the request path to the Jira API, allowing them to perform arbitrary GET requests using the Jira API credentials, potentially with elevated permissions, used by the application. 
Severity: nan (CVSS v3 base score: 4.1)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
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/discourse/discourse-jira
Files changed:
app/controllers/discourse_jira/issues_controller.rb
lib/discourse_jira/api.rb
spec/lib/api_spec.rb
spec/requests/issues_controller_spec.rb
Commit message: SECURITY: use `FinalDestination` to prevent SSRF attacks on API calls. (#50)

Also, it will check the issue key value on the "attach" action and won't allow the `/` character in it.

Previously, an administrator could point the Jira server to an arbitrary location. After entering an issue key or an arbitrary path in the “Attach Issue” feature, the request failed, however, the output of the response was reflected in the error logs. This vulnerability could be used to enumerate ports on the remote host locally, as well as retrieve responses from non-public-facing services, such as the Prometheus Ruby Exporter. While the path to the issue lookup was automatically appended by the application, it was possible to use the “…/” notation to manipulate the path from the interface. Therefore, any user with the ability to add an existing issue to a POST could perform arbitrary GET requests against the Jira API, however, only the administrator could see the output in the error logs.

Co-authored-by: David Taylor <david@taylorhq.com>

Vulnerable code:
        response = Api.get("issue/#{params[:issue_key]}")
              issue_key: json[:key],
              issue_url: URI.join(SiteSetting.discourse_jira_url, "browse/#{json[:key]}").to_s,
        post.jira_issue_key = result[:issue_key]
      Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
      make_request(endpoint) { |uri, headers| Net::HTTP::Get.new(uri, headers) }
        request = Net::HTTP::Post.new(uri, headers)

Fixed code:
      params.require(:issue_key)
      issue_key = params[:issue_key]
      raise Discourse::InvalidParameters.new(:issue_key) if issue_key.include?("/")

        response = Api.get("issue/#{issue_key}")
              issue_key: issue_key,
              issue_url: URI.join(SiteSetting.discourse_jira_url, "browse/#{issue_key}").to_s,
        post.jira_issue_key = issue_key
  class InvalidURI < StandardError
  end
      FinalDestination::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
    rescue FinalDestination::SSRFDetector::DisallowedIpError => e
      Discourse.warn_exception(e, message: "SSRF detected", env: { url: uri.to_s })
      raise InvalidURI
    rescue SocketError, Timeout::Error
      raise InvalidURI
      make_request(endpoint) { |uri, headers| FinalDestination::HTTP::Get.new(uri, headers) }
        request = FinalDestination::HTTP::Post.new(uri, headers)
    it "raises error for internal hosts" do
      WebMock.enable!(except: [:final_destination])
      FinalDestination::SSRFDetector.stubs(:lookup_ips).returns(["192.168.1.1"])
      Discourse.expects(:warn_exception).with(
        instance_of(FinalDestination::SSRFDetector::DisallowedIpError),
        message: "SSRF detected",
        env: {
          url: "https://jira.example.com/rest/api/2/serverInfo",
        },
      )
      expect { described_class.get_version! }.to raise_error(DiscourseJira::InvalidURI)
    ensure
      WebMock.enable!
    end

      FinalDestination::SSRFDetector.stubs(:lookup_ips).returns(["1.2.3.4"])
    it "requires issue key in correct format" do
      sign_in(admin)
      post = Fabricate(:post)

      post "/jira/issues/attach.json",
           params: {
             issue_key: "../DIS/42",
             topic_id: post.topic_id,
             post_number: post.post_number,
           }

      expect(response.status).to eq(400)
      expect(response.parsed_body["errors"][0]).to eq(
        I18n.t("invalid_params", message: "issue_key"),
      )
    end