119 - Baby Monitor Bugs, Grafana, and Twitter De-anonymization
Fundamentally, this is a cross-site request forgery (CSRF) issue, Grafana relies on two incomplete mitigations to defend against such attacks. First is the use of the SameSite
attribute by default, second is the validation of the Content-Type
header.
The first issue approached by jub0bs is the SameSite
cookies, this is what changes the attack from Cross-Site to Cross-Origin Request Forgery. Basically, SameSite
cookies will still be sent during “same-site” requests. Which includes a larger attack surface than you might expect if you, like me, have operated under the rough understanding that site and origin were basically the same. While the same origin is defined as having the same scheme, host and port, the same site more open. Two sites are considered to be the same site if the most specific public suffix (as defined by the Public Suffix List) plus 1 more domain component match.
Some services which offer user content on subdomain might be part of that public suffix list, such as “github.io” but for many coporate domains their public suffix will just be their top-level-domain. So the “same site” for them will just be restricted to being on the same domain “example.com”, including any subdomains (“a.b.c” and x.y.z.b.c” would be on the site “b.c”). Which is still a significant attack surface for many environments.
The second issue, the Content-Type
validation really surprised me. For cross-origin requests, the CORS headers come into play and preflight requests. I held what I believe was a fairly common belief, that changing the Content-Type
header away from the usual suspects: text/plain
, application/x-www-form-urlencoded
, or multipart/form-data
would result in a preflight reqquest that would ultimately kill the request. This isn’t precisely true, atleast for the fetch
API, which only requires that the “essence” of the Content-Type header be one of the above. You can, without triggering a preflight have other attributes included in the header. So “text/plain; application/json” would be allowed through without a preflight.
As Grafana only looked for the presence of the keyword “json” in the Content-Type
header this too could be bypassed, meaning all of the authenticated API endpoints could be exposed to CORF.
Multiple vulnerabilities, mostly stemming from poor architectural design that leaves the system pretty much wide-open.
Leaking User IDs - The first issue is just the ability for anyone to connect to the MQTT server running on eu.nooie.com
and subscribing to /device/init
topic. This topic will publish an event whenever a new device comes online and that includes the device user ids (a uuid and uid).
Accessing RTSPS feeds of arbitrary cameras - Once you had the uuid
for a camera, you could publish messages to /device/<uuid>/cmd
including a cmd
and url
. When cmd
is 10
and the url
is a URL for the device to upload its camera feed to. Normally this URL would be a relay server used by Nooie to relay the feed to the client device, but it can be any url, and it only requires knowledge of the camera’s unique id.
Stack-based buffer overflow leading to remote code execution - A pretty straight-forward buffer overflow onto the stack, where a host
value is extracted from input, and copied into a fixed size 32-byte buffer. One can overflow that, and without stack canaries or ASLR exploitation was just a ret2libc attack.
Obtaining AWS credentials for a specific camera - The /rest/v2/device/get_awstoken
endpoint could be used to retrieve AWS credentials that can be used to store recordings within AWS, again this just used the uuid
and uid
that could be retrieved from the first issue, so one could get these credentials for any camera that comes online.
Accessing the entire AWS bucket - Making the last issue even worse, although each camera had its own AWS credentials, those credentials were scoped for the entire S3 bucket, meaning each camera could access any other camera’s uploads.
Using the onboarding API endpoint for the Android Twitter client it is possible to reveal the user_id
for any phone number or email. As part of the onboarding flow there is the LoginEnterUserIdentifier
subtask, where you, as the name implies enter your user identifier like an email or phone number. This value is used in an AccountDuplicationCheck
and if an account already exists, the response will include the user_id
. This happens regardless of the privacy settings of the account which may disable account discoverability by phone/email.
A simple smart contract issue this week from an actual theft, and the problem, being too trusting. The makeHop
function allows users to transfer their shares between different staking pools. Call it with the address of the new pool, it’ll approve the _newPool
address to transfer tokens out, and call receiveHop
on the _newPool
with the share amount and the initiator expecting it to actually perform the transfer. The problem is that it calls .approve
for the totalSupply()
of the pool, trusting that the receiving pool will only transfer what its supposed to. A malicious user can call makeHop
to a malicious pool they control, and take advantage of the approval to drain all the funds rather than just the user’s shares.