Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

security: Chrome debugging protocol is open to anyone on localhost #43

Open
tv42 opened this issue Feb 14, 2019 · 7 comments
Open

security: Chrome debugging protocol is open to anyone on localhost #43

tv42 opened this issue Feb 14, 2019 · 7 comments

Comments

@tv42
Copy link

tv42 commented Feb 14, 2019

Hi. Lorca currently exposes all information about the UI to all processes on localhost, and allows any local process to hijack the UI.

https://peter.sh/experiments/chromium-command-line-switches/#remote-debugging-address

$ mkdir z
$ cd z
$ go mod init demo
go: creating new go.mod: module demo
$ curl https://raw.githubusercontent.com/zserge/lorca/master/examples/hello/main.go >main.go
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   408  100   408    0     0    542      0 --:--:-- --:--:-- --:--:--   541
$ go run main.go
go: finding github.com/zserge/lorca v0.1.6
go: downloading github.com/zserge/lorca v0.1.6
go: extracting github.com/zserge/lorca v0.1.6

As the attacker:

$ ss -ltnp |grep chrome
LISTEN   0         10                127.0.0.1:42381            0.0.0.0:*        users:(("chrome",pid=1703,fd=98))                                              
$ google-chrome http://127.0.0.1:42381
# observe the UI contents happily

I would recommend you switch to --remote-debugging-pipe: https://github.com/chromium/chromium/blob/d925e7f357d93b95631c22c47e2cc93b093dacc5/content/public/common/content_switches.cc#L700-L703

@zserge
Copy link
Owner

zserge commented Feb 15, 2019

@tv42 Totally support your idea, and this was the original intention. However, pipes are not well supported on Windows (at least in Go). So if you have an idea how it can be implemented on Windows without much cgo hassle - please let me know!

@tv42
Copy link
Author

tv42 commented Feb 15, 2019

Oh, funny, because I understood the motivation for implementing the pipe functionality in Chrome was that the original FD passing mechanism wasn't good for Windows.

My understanding is that os.Pipe and os/exec.Cmd.ExtraFiles work on Windows just like they would on anything else (click those links to see the implementation), but I don't "do Windows" so I'm not the right person to ask.

Even if this was something where Windows was not capable of doing a thing, the current mechanism essentially makes Lorca unusable for anything beyond a demo. Really.

@zserge
Copy link
Owner

zserge commented Feb 15, 2019

@tv42 Actually, in the exec.Cmd.ExtraFiles comment it states: // ExtraFiles is not supported on Windows. and it seems to be true.

@tv42
Copy link
Author

tv42 commented Feb 15, 2019

Oh. Funky. Sorry for missing that. This seems to be the relevant Go issue: golang/go#21085

Based on the that issue, it sounds like this restriction in Go comes from Windows file handles not being sequentially numbered, but then the Chrome docs talking about fd 3/4 get confusing. Plus, their issue tracking implies -pipe won over -fd because of Windows support ("I don't think you can wrap single pipe with TCP socket in Windows" is the argument against using a single fd in https://chromium-review.googlesource.com/c/chromium/src/+/954405/ ). I added a comment to the Go issue in hopes that someone will know if these puzzle pieces connect.

Chasing down what happens on Windows, links to interesting bits in source/docs:

https://cs.chromium.org/chromium/src/content/browser/devtools/devtools_pipe_handler.cc?q=read_handle_&l=49&dr=C
https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2017

Here's evidence of things using fds 3/4 happily, with further resources in comments. Puppeteer especially is specially advertised as working on Windows: https://pptr.dev

puppeteer/puppeteer#2079 (comment)
GoogleChromeLabs/carlo#11
cyrus-and/chrome-remote-interface#381

@Srinivasa314
Copy link
Contributor

Srinivasa314 commented May 27, 2020

I managed to use pipes in my library https://github.com/Srinivasa314/alcro in the master branch, but you need to use cgo for it. Also headless mode does not work (I dont know why)

@tv42
Copy link
Author

tv42 commented Oct 29, 2021

For what it's worth, golang/go#21085 is now closed, and thus this should be fixable?

@rtpt-alexanderneumann
Copy link

rtpt-alexanderneumann commented Jan 13, 2022

FWIW, I've implemented the pipes solution for Linux (and it works great), please let me know if that's something you'd like to add (even if it does not implement it on Windows).

I've split out the websocket connection, built a Channel interface, and here's how to connect to Chrome via pipes:

package lorca

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
)

// Channel exchanges messages with the running browser instance.
type Channel interface {
	Send(interface{}) error
	Receive(interface{}) error
	Close() error
}

type pipeChannel struct {
	pout io.WriteCloser
	pin  io.ReadCloser
	rd   *bufio.Reader
}

func runChromeWithPipes(chromeBinary string, args ...string) (*exec.Cmd, Channel, error) {
	rIn, wIn, err := os.Pipe()
	if err != nil {
		return nil, nil, fmt.Errorf("create pipe failed: %w", err)
	}

	rOut, wOut, err := os.Pipe()
	if err != nil {
		return nil, nil, fmt.Errorf("create pipe failed: %w", err)
	}

	// Start chrome process
        args = append(args, "--remote-debugging-pipe")
	cmd := exec.Command(chromeBinary, args...)
	cmd.ExtraFiles = []*os.File{rIn, wOut}

	if err != nil {
		return nil, nil, err
	}

	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}

	ch := &pipeChannel{
		pout: wIn,
		pin:  rOut,
		rd:   bufio.NewReader(rOut),
	}
	return cmd, ch, nil
}

func (ch *pipeChannel) Close() error {
	err := ch.pout.Close()
	errOut := ch.pin.Close()

	if err == nil {
		err = errOut
	}

	if err != nil {
		return fmt.Errorf("close pipe channel failed: %w", err)
	}

	return nil
}

func (ch *pipeChannel) Send(obj interface{}) error {
	buf, err := json.Marshal(obj)
	if err != nil {
		return fmt.Errorf("json encode failed: %w", err)
	}

	// terminate the message with a null byte
	buf = append(buf, 0)

	_, err = ch.pout.Write(buf)
	if err != nil {
		return fmt.Errorf("write to chrome instance failed: %w", err)
	}

	return nil
}

func (ch *pipeChannel) Receive(obj interface{}) error {
	// read until the next null byte
	buf, err := ch.rd.ReadBytes(0)
	if err != nil {
		return fmt.Errorf("read message from chrome failed: %w", err)
	}

	buf = bytes.TrimRight(buf, "\x00")

	err = json.Unmarshal(buf, obj)
	if err != nil {
		return fmt.Errorf("json unmarshal failed: %w", err)
	}

	return nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants