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.