Skip to content

Commit

Permalink
big changes but testing 2s before release
Browse files Browse the repository at this point in the history
  • Loading branch information
mxrch committed Jan 16, 2024
1 parent e873469 commit f37efa4
Show file tree
Hide file tree
Showing 23 changed files with 330 additions and 186 deletions.
4 changes: 2 additions & 2 deletions examples/email_registered.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import httpx
import trio
import asyncio

import sys

Expand All @@ -17,4 +17,4 @@ async def main():

print("Registered on Google :", is_registered)

trio.run(main) # running our async code in a non-async code
asyncio.run(main()) # running our async code in a non-async code
4 changes: 2 additions & 2 deletions examples/get_people_name.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import httpx
import trio

import asyncio
import sys

from ghunt.apis.peoplepa import PeoplePaHttp
Expand Down Expand Up @@ -35,4 +35,4 @@ async def main():
else:
print("Not existing globally.")

trio.run(main) # running our async code in a non-async code
asyncio.run(main()) # running our async code in a non-async code
56 changes: 56 additions & 0 deletions ghunt/apis/accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI

import httpx

from typing import *
import inspect


class Accounts(GAPI):
def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
super().__init__()

if not headers:
headers = gb.config.headers

base_headers = {}

headers = {**headers, **base_headers}

# Android OAuth fields
self.api_name = "chrome"
self.package_name = "com.android.chrome"
self.scopes = [
"https://www.google.com/accounts/OAuthLogin"
]

self.hostname = "accounts.google.com"
self.scheme = "https"

self.authentication_mode = "oauth" # sapisidhash, cookies_only, oauth or None
self.require_key = None # key name, or None

self._load_api(creds, headers)

async def OAuthLogin(self, as_client: httpx.AsyncClient) -> str:
endpoint_name = inspect.currentframe().f_code.co_name

verb = "GET"
base_url = f"/OAuthLogin"
data_type = None # json, data or None

params = {
"source": "ChromiumBrowser",
"issueuberauth": 1
}

self._load_endpoint(endpoint_name)
req = await self._query(as_client, verb, endpoint_name, base_url, params, None, data_type)

# Parsing
uber_auth = req.text

return True, uber_auth
2 changes: 1 addition & 1 deletion ghunt/apis/peoplepa.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ async def people(self, as_client: httpx.AsyncClient, gaia_id: str, params_templa
"extension_set.extension_names": [
"DYNAMITE_ADDITIONAL_DATA",
"DYNAMITE_ORGANIZATION_INFO",
"GPLUS_ADDITIONAL_DATA"
# "GPLUS_ADDITIONAL_DATA"
],
"request_mask.include_field.paths": [
"person.metadata.best_display_name",
Expand Down
10 changes: 5 additions & 5 deletions ghunt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ def parse_and_run():
process_args(args)

def process_args(args: argparse.Namespace):
import trio
import asyncio
match args.module:
case "login":
from ghunt.modules import login
trio.run(login.check_and_login, None, args.clean)
asyncio.run(login.check_and_login(None, args.clean))
case "email":
from ghunt.modules import email
trio.run(email.hunt, None, args.email_address, args.json)
asyncio.run(email.hunt(None, args.email_address, args.json))
case "gaia":
from ghunt.modules import gaia
trio.run(gaia.hunt, None, args.gaia_id, args.json)
asyncio.run(gaia.hunt(None, args.gaia_id, args.json))
case "drive":
from ghunt.modules import drive
trio.run(drive.hunt, None, args.file_id, args.json)
asyncio.run(drive.hunt(None, args.file_id, args.json))
6 changes: 6 additions & 0 deletions ghunt/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ class GHuntOSIDAuthError(BaseException):
class GHuntCredsNotLoaded(BaseException):
pass

class GHuntInvalidSession(BaseException):
pass

class GHuntNotAuthenticated(BaseException):
pass

class GHuntInvalidTarget(BaseException):
pass

class GHuntLoginError(BaseException):
pass
3 changes: 3 additions & 0 deletions ghunt/ghunt.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ def main():

from ghunt.cli import parse_and_run
from ghunt.helpers.banner import show_banner
from ghunt.helpers.utils import show_version

show_banner()
show_version()
print()
parse_and_run()
160 changes: 108 additions & 52 deletions ghunt/helpers/auth.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import asyncio
import json
import base64
from typing import *
from copy import deepcopy

import httpx
from bs4 import BeautifulSoup as bs

from ghunt import globals as gb
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
from ghunt.helpers.utils import *
from ghunt.helpers import listener
from ghunt.helpers.knowledge import get_domain_of_service, get_package_sig
from ghunt.knowledge.services import services_baseurls
from ghunt.helpers.auth import *


Expand Down Expand Up @@ -61,62 +63,68 @@ async def android_oauth_app(as_client: httpx.AsyncClient, master_token: str,
raise GHuntAndroidAppOAuth2Error(f'Expected "{keyword}" in the response of the Android App OAuth2 Authentication.\nThe master token may be revoked.')
return resp["Auth"], resp["grantedScopes"].split(" "), int(resp["Expiry"])

async def gen_osids(cookies: Dict[str, str], osids: List[str]) -> Dict[str, str]:
"""
Generate OSIDs of given services names,
contained in the "osids" dict argument.
"""
generated_osids = {}
for service in osids:
sample_cookies = deepcopy(cookies)
domain = get_domain_of_service(service)
req = httpx.get(f"https://accounts.google.com/ServiceLogin?service={service}&osid=1&continue=https://{domain}/&followup=https://{domain}/&authuser=0",
cookies=cookies, headers=gb.config.headers)
async def gen_osid(as_client: httpx.AsyncClient, cookies: Dict[str, str], generated_osids: dict[str, str], service: str) -> None:
domain = get_domain_of_service(service)

params = {
"service": service,
"osid": 1,
"continue": f"https://{domain}/",
"followup": f"https://{domain}/",
"authuser": 0
}

for cookie in ["__Host-GAPS", "SIDCC", "__Secure-3PSIDCC"]:
sample_cookies[cookie] = req.cookies[cookie]
req = await as_client.get(f"https://accounts.google.com/ServiceLogin", params=params, cookies=cookies, headers=gb.config.headers)

body = bs(req.text, 'html.parser')
params = {x.attrs["name"]:x.attrs["value"] for x in body.find_all("input", {"type":"hidden"})}
body = bs(req.text, 'html.parser')

params = {x.attrs["name"]:x.attrs["value"] for x in body.find_all("input", {"type":"hidden"})}

headers = {**gb.config.headers, **{"Content-Type": "application/x-www-form-urlencoded"}}
req = httpx.post(f"https://{domain}/accounts/SetOSID", cookies=cookies, data=params, headers=headers)
headers = {**gb.config.headers, **{"Content-Type": "application/x-www-form-urlencoded"}}
req = await as_client.post(f"https://{domain}/accounts/SetOSID", cookies=cookies, data=params, headers=headers)

if not "OSID" in req.cookies:
raise GHuntOSIDAuthError("[-] No OSID header detected, exiting...")
if not "OSID" in req.cookies:
raise GHuntOSIDAuthError("[-] No OSID header detected, exiting...")

generated_osids[service] = req.cookies["OSID"]
generated_osids[service] = req.cookies["OSID"]

async def gen_osids(as_client: httpx.AsyncClient, cookies: Dict[str, str], osids: List[str]) -> Dict[str, str]:
"""
Generate OSIDs of given services names,
contained in the "osids" dict argument.
"""
generated_osids = {}
tasks = [gen_osid(as_client, cookies, generated_osids, service) for service in osids]
await asyncio.gather(*tasks)

return generated_osids

def check_cookies(cookies: Dict[str, str]) -> bool:
async def check_cookies(as_client: httpx.AsyncClient, cookies: Dict[str, str]) -> bool:
"""Checks the validity of given cookies."""
req = httpx.get("https://docs.google.com", cookies=cookies, headers=gb.config.headers)
if req.status_code != 307:
return False

set_cookies = extract_set_cookies(req)
if any([cookie in set_cookies for cookie in cookies]):
continue_url = "https://www.google.com/robots.txt"
params = {"continue": continue_url}
req = await as_client.get("https://accounts.google.com/CheckCookie", params=params, cookies=cookies)
return req.status_code == 302 and not req.headers.get("Location", "").startswith(("https://support.google.com", "https://accounts.google.com/CookieMismatch"))

async def check_osid(as_client: httpx.AsyncClient, cookies: Dict[str, str], service: str) -> bool:
"""Checks the validity of given OSID."""
domain = get_domain_of_service(service)
wanted = ["authuser", "continue", "osidt", "ifkv"]
req = await as_client.get(f"https://accounts.google.com/ServiceLogin?service={service}&osid=1&continue=https://{domain}/&followup=https://{domain}/&authuser=0",
cookies=cookies, headers=gb.config.headers)

body = bs(req.text, 'html.parser')
params = [x.attrs["name"] for x in body.find_all("input", {"type":"hidden"})]
if not all([param in wanted for param in params]):
return False

return True

def check_osids(cookies: Dict[str, str], osids: Dict[str, str]) -> bool:
async def check_osids(as_client: httpx.AsyncClient, cookies: Dict[str, str], osids: Dict[str, str]) -> bool:
"""Checks the validity of given OSIDs."""
for service in osids:
domain = get_domain_of_service(service)
cookies_with_osid = inject_osid(cookies, osids, service)
wanted = ["authuser", "continue", "osidt", "ifkv"]
req = httpx.get(f"https://accounts.google.com/ServiceLogin?service={service}&osid=1&continue=https://{domain}/&followup=https://{domain}/&authuser=0",
cookies=cookies_with_osid, headers=gb.config.headers)

body = bs(req.text, 'html.parser')
params = [x.attrs["name"] for x in body.find_all("input", {"type":"hidden"})]
if not all([param in wanted for param in params]):
return False

return True
tasks = [check_osid(as_client, cookies, service) for service in osids]
results = await asyncio.gather(*tasks)
return all(results)

async def check_master_token(as_client: httpx.AsyncClient, master_token: str) -> str:
"""Checks the validity of the android master token."""
Expand All @@ -126,34 +134,82 @@ async def check_master_token(as_client: httpx.AsyncClient, master_token: str) ->
return False
return True

async def getting_cookies_dialog(cookies: Dict[str, str]) -> Tuple[Dict[str, str], str] :
async def gen_cookies_and_osids(as_client: httpx.AsyncClient, ghunt_creds: GHuntCreds, osids: list[str]=[*services_baseurls.keys()]):
from ghunt.apis.accounts import Accounts
accounts_api = Accounts(ghunt_creds)
is_logged_in, uber_auth = await accounts_api.OAuthLogin(as_client)
if not is_logged_in:
raise GHuntLoginError("[-] Not logged in.")

params = {
"uberauth": uber_auth,
"continue": "https://www.google.com",
"source": "ChromiumAccountReconcilor",
"externalCcResult": "doubleclick:null,youtube:null"
}

req = await as_client.get("https://accounts.google.com/MergeSession", params=params)
cookies = dict(req.cookies)
ghunt_creds.cookies = cookies
osids = await gen_osids(as_client, cookies, osids)
ghunt_creds.osids = osids

async def check_and_gen(as_client: httpx.AsyncClient, ghunt_creds: GHuntCreds):
"""Checks the validity of the cookies and generate new ones if needed."""
if not await check_cookies(as_client, ghunt_creds.cookies):
await gen_cookies_and_osids(as_client, ghunt_creds)
if not await check_cookies(as_client, ghunt_creds.cookies):
raise GHuntLoginError("[-] Can't generate cookies after multiple retries. Exiting...")

ghunt_creds.save_creds(silent=True)
gb.rc.print("[+] Authenticated !\n", style="sea_green3")

def auth_dialog() -> Tuple[Dict[str, str], str] :
"""
Launch the dialog that asks the user
how he want to generate its credentials.
"""
choices = ("You can facilitate configuring GHunt by using the GHunt Companion extension on Firefox, Chrome, Edge and Opera here :\n"
"=> https://github.com/mxrch/ghunt_companion\n\n"
"[1] (Companion) Put GHunt on listening mode (currently not compatible with docker)\n"
"[2] (Companion) Paste base64-encoded cookies\n"
"[3] Enter manually all cookies\n\n"
"[2] (Companion) Paste base64-encoded authentication\n"
"[3] Enter the oauth_token (stats with \"oauth2_4/\")\n"
"[4] Enter the master token (starts with \"aas_et/\")\n"
"Choice => ")

oauth_token = ""
master_token = ""
choice = input(choices)
if choice in ["1", "2"]:
if choice == "1":
received_data = listener.run()
elif choice == "2":
received_data = input("Paste the encoded cookies here => ")
received_data = input("Paste the encoded credentials here => ")
data = json.loads(base64.b64decode(received_data))
cookies = data["cookies"]
oauth_token = data["oauth_token"]

elif choice == "3":
for name in cookies.keys():
cookies[name] = input(f"{name} => ").strip().strip('"')
oauth_token = input(f"oauth_token").strip().strip('"')
oauth_token = input(f"OAuth token => ").strip('" ')

elif choice == "4":
master_token = input(f"Master token => ").strip('" ')

else:
exit("Please choose a valid choice. Exiting...")

return cookies, oauth_token
return oauth_token, master_token

async def load_and_auth(as_client: httpx.AsyncClient, help=True) -> GHuntCreds:
"""Returns an authenticated GHuntCreds object."""
creds = GHuntCreds()
try:
creds.load_creds()
except GHuntInvalidSession as e:
if help:
raise GHuntInvalidSession(f"Please generate a new session by doing => ghunt login") from e
else:
raise e

await check_and_gen(as_client, creds)

return creds
4 changes: 2 additions & 2 deletions ghunt/helpers/ia.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from ghunt.apis.vision import VisionHttp

import httpx
import trio

from base64 import b64encode
import asyncio


async def detect_face(vision_api: VisionHttp, as_client: httpx.AsyncClient, image_url: str) -> None:
Expand All @@ -18,7 +18,7 @@ async def detect_face(vision_api: VisionHttp, as_client: httpx.AsyncClient, imag
rate_limited, are_faces_found, faces_results = await vision_api.detect_faces(as_client, image_content=encoded_image)
if not rate_limited:
break
await trio.sleep(0.5)
await asyncio.sleep(0.5)
else:
exit("\n[-] Vision API keeps rate-limiting.")

Expand Down
Loading

0 comments on commit f37efa4

Please sign in to comment.