skip to content
Back to
Home Bounties Research Advisories CodeQL Wall of Fame Get Involved Events
June 14, 2023

GHSL-2023-025: Drive-by command injection in SRS's api-server - CVE-2023-34105

Alvaro Munoz

Coordinated Disclosure Timeline


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



Tested Version



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{}{})

		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() {
			} 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://", sm.App, sm.Stream, sm.Vhost)
	if _, ok := v.snapshots.Load(streamUrl); ok {
	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 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.


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




This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz), using the Command Injection CodeQL query.


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