Coordinated Disclosure Timeline
- 2023-02-08: Reported issue via GitHub’s PVR.
- 2023-06-04: Issue is acknowledged.
- 2023-06-05: Issue fixed inv5.0.157 and v6.0.48.
Summary
SRS’s api-server
server is vulnerable to a drive-by command injection.
Product
SRS
Tested Version
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).
CVE
- CVE-2023-34105
Resources
- https://github.com/ossrs/srs/security/advisories/GHSA-vpr5-779c-cx62
Credit
This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz), using the Command Injection CodeQL query.
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2023-025
in any communication regarding this issue.