create_session_curl.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #!/usr/bin/env python3
  2. """
  3. DEPRECATED: X now requires a castle token generated by client-side JavaScript
  4. during the login flow, which blocks pure-API authentication. Use
  5. create_session_browser.py instead.
  6. This script is kept for reference but will fail with current X API defenses.
  7. Requirements:
  8. pip install curl_cffi pyotp
  9. Usage:
  10. python3 tools/create_session_curl.py <username> <password> [totp_seed] [--append sessions.jsonl]
  11. Examples:
  12. # Output to terminal
  13. python3 tools/create_session_curl.py myusername mypassword TOTP_SECRET
  14. # Append to sessions.jsonl
  15. python3 tools/create_session_curl.py myusername mypassword TOTP_SECRET --append sessions.jsonl
  16. Output:
  17. {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."}
  18. """
  19. import sys
  20. import json
  21. import pyotp
  22. from curl_cffi import requests
  23. BEARER_TOKEN = "AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF"
  24. BASE_URL = "https://api.x.com/1.1/onboarding/task.json"
  25. GUEST_ACTIVATE_URL = "https://api.x.com/1.1/guest/activate.json"
  26. # Subtask versions required by API
  27. SUBTASK_VERSIONS = {
  28. "action_list": 2, "alert_dialog": 1, "app_download_cta": 1,
  29. "check_logged_in_account": 2, "choice_selection": 3,
  30. "contacts_live_sync_permission_prompt": 0, "cta": 7, "email_verification": 2,
  31. "end_flow": 1, "enter_date": 1, "enter_email": 2, "enter_password": 5,
  32. "enter_phone": 2, "enter_recaptcha": 1, "enter_text": 5, "generic_urt": 3,
  33. "in_app_notification": 1, "interest_picker": 3, "js_instrumentation": 1,
  34. "menu_dialog": 1, "notifications_permission_prompt": 2, "open_account": 2,
  35. "open_home_timeline": 1, "open_link": 1, "phone_verification": 4,
  36. "privacy_options": 1, "security_key": 3, "select_avatar": 4,
  37. "select_banner": 2, "settings_list": 7, "show_code": 1, "sign_up": 2,
  38. "sign_up_review": 4, "tweet_selection_urt": 1, "update_users": 1,
  39. "upload_media": 1, "user_recommendations_list": 4,
  40. "user_recommendations_urt": 1, "wait_spinner": 3, "web_modal": 1
  41. }
  42. def get_base_headers(guest_token=None):
  43. """Build base headers for API requests."""
  44. headers = {
  45. "Authorization": f"Bearer {BEARER_TOKEN}",
  46. "Content-Type": "application/json",
  47. "Accept": "*/*",
  48. "Accept-Language": "en-US",
  49. "X-Twitter-Client-Language": "en-US",
  50. "Origin": "https://x.com",
  51. "Referer": "https://x.com/",
  52. }
  53. if guest_token:
  54. headers["X-Guest-Token"] = guest_token
  55. return headers
  56. def get_cookies_dict(session):
  57. """Extract cookies from session."""
  58. return session.cookies.get_dict() if hasattr(session.cookies, 'get_dict') else dict(session.cookies)
  59. def make_request(session, headers, flow_token, subtask_data, print_msg):
  60. """Generic request handler for flow steps."""
  61. print(f"[*] {print_msg}...", file=sys.stderr)
  62. payload = {
  63. "flow_token": flow_token,
  64. "subtask_inputs": [subtask_data] if isinstance(subtask_data, dict) else subtask_data
  65. }
  66. response = session.post(BASE_URL, json=payload, headers=headers)
  67. response.raise_for_status()
  68. data = response.json()
  69. new_flow_token = data.get('flow_token')
  70. if not new_flow_token:
  71. raise Exception(f"Failed to get flow token: {print_msg}")
  72. return new_flow_token, data
  73. def get_guest_token(session):
  74. """Get guest token for unauthenticated requests."""
  75. print("[*] Getting guest token...", file=sys.stderr)
  76. response = session.post(GUEST_ACTIVATE_URL, headers={"Authorization": f"Bearer {BEARER_TOKEN}"})
  77. response.raise_for_status()
  78. guest_token = response.json().get('guest_token')
  79. if not guest_token:
  80. raise Exception("Failed to obtain guest token")
  81. print(f"[*] Got guest token: {guest_token}", file=sys.stderr)
  82. return guest_token
  83. def init_flow(session, guest_token):
  84. """Initialize the login flow."""
  85. print("[*] Initializing login flow...", file=sys.stderr)
  86. headers = get_base_headers(guest_token)
  87. payload = {
  88. "input_flow_data": {
  89. "flow_context": {
  90. "debug_overrides": {},
  91. "start_location": {"location": "manual_link"}
  92. },
  93. "subtask_versions": SUBTASK_VERSIONS
  94. }
  95. }
  96. response = session.post(f"{BASE_URL}?flow_name=login", json=payload, headers=headers)
  97. response.raise_for_status()
  98. flow_token = response.json().get('flow_token')
  99. if not flow_token:
  100. raise Exception("Failed to get initial flow token")
  101. print("[*] Got initial flow token", file=sys.stderr)
  102. return flow_token, headers
  103. def submit_username(session, flow_token, headers, guest_token, username):
  104. """Submit username."""
  105. headers = headers.copy()
  106. headers["X-Guest-Token"] = guest_token
  107. subtask = {
  108. "subtask_id": "LoginEnterUserIdentifierSSO",
  109. "settings_list": {
  110. "setting_responses": [{
  111. "key": "user_identifier",
  112. "response_data": {"text_data": {"result": username}}
  113. }],
  114. "link": "next_link"
  115. }
  116. }
  117. flow_token, data = make_request(session, headers, flow_token, subtask, "Submitting username")
  118. # Check for denial (suspicious activity)
  119. if data.get('subtasks') and 'cta' in data['subtasks'][0]:
  120. error_msg = data['subtasks'][0]['cta'].get('primary_text', {}).get('text')
  121. if error_msg:
  122. raise Exception(f"Login denied: {error_msg}")
  123. return flow_token
  124. def submit_password(session, flow_token, headers, guest_token, password):
  125. """Submit password and detect if 2FA is needed."""
  126. headers = headers.copy()
  127. headers["X-Guest-Token"] = guest_token
  128. subtask = {
  129. "subtask_id": "LoginEnterPassword",
  130. "enter_password": {"password": password, "link": "next_link"}
  131. }
  132. flow_token, data = make_request(session, headers, flow_token, subtask, "Submitting password")
  133. needs_2fa = any(s.get('subtask_id') == 'LoginTwoFactorAuthChallenge' for s in data.get('subtasks', []))
  134. if needs_2fa:
  135. print("[*] 2FA required", file=sys.stderr)
  136. return flow_token, needs_2fa
  137. def submit_2fa(session, flow_token, headers, guest_token, totp_seed):
  138. """Submit 2FA code."""
  139. if not totp_seed:
  140. raise Exception("2FA required but no TOTP seed provided")
  141. code = pyotp.TOTP(totp_seed).now()
  142. print("[*] Generating 2FA code...", file=sys.stderr)
  143. headers = headers.copy()
  144. headers["X-Guest-Token"] = guest_token
  145. subtask = {
  146. "subtask_id": "LoginTwoFactorAuthChallenge",
  147. "enter_text": {"text": code, "link": "next_link"}
  148. }
  149. flow_token, _ = make_request(session, headers, flow_token, subtask, "Submitting 2FA code")
  150. return flow_token
  151. def submit_js_instrumentation(session, flow_token, headers, guest_token):
  152. """Submit JS instrumentation response."""
  153. headers = headers.copy()
  154. headers["X-Guest-Token"] = guest_token
  155. subtask = {
  156. "subtask_id": "LoginJsInstrumentationSubtask",
  157. "js_instrumentation": {
  158. "response": '{"rf":{"a4fc506d24bb4843c48a1966940c2796bf4fb7617a2d515ad3297b7df6b459b6":121,"bff66e16f1d7ea28c04653dc32479cf416a9c8b67c80cb8ad533b2a44fee82a3":-1,"ac4008077a7e6ca03210159dbe2134dea72a616f03832178314bb9931645e4f7":-22,"c3a8a81a9b2706c6fec42c771da65a9597c537b8e4d9b39e8e58de9fe31ff239":-12},"s":"ZHYaDA9iXRxOl2J3AZ9cc23iJx-Fg5E82KIBA_fgeZFugZGYzRtf8Bl3EUeeYgsK30gLFD2jTQx9fAMsnYCw0j8ahEy4Pb5siM5zD6n7YgOeWmFFaXoTwaGY4H0o-jQnZi5yWZRAnFi4lVuCVouNz_xd2BO2sobCO7QuyOsOxQn2CWx7bjD8vPAzT5BS1mICqUWyjZDjLnRZJU6cSQG5YFIHEPBa8Kj-v1JFgkdAfAMIdVvP7C80HWoOqYivQR7IBuOAI4xCeLQEdxlGeT-JYStlP9dcU5St7jI6ExyMeQnRicOcxXLXsan8i5Joautk2M8dAJFByzBaG4wtrPhQ3QAAAZEi-_t7"}',
  159. "link": "next_link"
  160. }
  161. }
  162. flow_token, _ = make_request(session, headers, flow_token, subtask, "Submitting JS instrumentation")
  163. return flow_token
  164. def complete_flow(session, flow_token, headers):
  165. """Complete the login flow."""
  166. cookies = get_cookies_dict(session)
  167. headers = headers.copy()
  168. headers["X-Twitter-Auth-Type"] = "OAuth2Session"
  169. if cookies.get('ct0'):
  170. headers["X-Csrf-Token"] = cookies['ct0']
  171. subtask = {
  172. "subtask_id": "AccountDuplicationCheck",
  173. "check_logged_in_account": {"link": "AccountDuplicationCheck_false"}
  174. }
  175. make_request(session, headers, flow_token, subtask, "Completing login flow")
  176. def extract_user_id(cookies_dict):
  177. """Extract user ID from twid cookie."""
  178. twid = cookies_dict.get('twid', '').strip('"')
  179. for prefix in ['u=', 'u%3D']:
  180. if prefix in twid:
  181. return twid.split(prefix)[1].split('&')[0].strip('"')
  182. return None
  183. def login_and_get_cookies(username, password, totp_seed=None):
  184. """Authenticate with X.com and extract session cookies."""
  185. session = requests.Session(impersonate="chrome")
  186. try:
  187. guest_token = get_guest_token(session)
  188. flow_token, headers = init_flow(session, guest_token)
  189. flow_token = submit_js_instrumentation(session, flow_token, headers, guest_token)
  190. flow_token = submit_username(session, flow_token, headers, guest_token, username)
  191. flow_token, needs_2fa = submit_password(session, flow_token, headers, guest_token, password)
  192. if needs_2fa:
  193. flow_token = submit_2fa(session, flow_token, headers, guest_token, totp_seed)
  194. complete_flow(session, flow_token, headers)
  195. cookies_dict = get_cookies_dict(session)
  196. cookies_dict['username'] = username
  197. user_id = extract_user_id(cookies_dict)
  198. if user_id:
  199. cookies_dict['id'] = user_id
  200. print("[*] Successfully authenticated", file=sys.stderr)
  201. return cookies_dict
  202. finally:
  203. session.close()
  204. def main():
  205. if len(sys.argv) < 3:
  206. print('Usage: python3 create_session_curl.py username password [totp_seed] [--append sessions.jsonl]', file=sys.stderr)
  207. sys.exit(1)
  208. username = sys.argv[1]
  209. password = sys.argv[2]
  210. totp_seed = None
  211. append_file = None
  212. # Parse optional arguments
  213. i = 3
  214. while i < len(sys.argv):
  215. arg = sys.argv[i]
  216. if arg == '--append':
  217. if i + 1 < len(sys.argv):
  218. append_file = sys.argv[i + 1]
  219. i += 2
  220. else:
  221. print('[!] Error: --append requires a filename', file=sys.stderr)
  222. sys.exit(1)
  223. elif not arg.startswith('--'):
  224. if totp_seed is None:
  225. totp_seed = arg
  226. i += 1
  227. else:
  228. print(f'[!] Warning: Unknown argument: {arg}', file=sys.stderr)
  229. i += 1
  230. try:
  231. cookies = login_and_get_cookies(username, password, totp_seed)
  232. session = {
  233. 'kind': 'cookie',
  234. 'username': cookies['username'],
  235. 'id': cookies.get('id'),
  236. 'auth_token': cookies['auth_token'],
  237. 'ct0': cookies['ct0']
  238. }
  239. output = json.dumps(session)
  240. if append_file:
  241. with open(append_file, 'a') as f:
  242. f.write(output + '\n')
  243. print(f'✓ Session appended to {append_file}', file=sys.stderr)
  244. else:
  245. print(output)
  246. sys.exit(0)
  247. except Exception as error:
  248. print(f'[!] Error: {error}', file=sys.stderr)
  249. import traceback
  250. traceback.print_exc(file=sys.stderr)
  251. sys.exit(1)
  252. if __name__ == '__main__':
  253. main()