Tags: Python Crawler, Reverse Analysis, RSA Encryption, Requests, BeautifulSoup
Environment: Windows 10 x64 + Python 3.11 + requests 2.32 + beautifulsoup4 4.12
Example Site: The domain names and cookie names in this article have been desensitized and replaced with placeholders likeexample.edu.cn/learning.example.edu.cn, for technical research only.
1 Origin: Why Write This Script?
In many university or enterprise online teaching/academic platforms, CAS Single Sign-On (Central Authentication Service) is commonly used. When we want to periodically fetch schedules, download materials, or perform automatic check-ins, repeatedly entering the student ID and password manually is extremely inconvenient, hence the need for a "headless" script to automate login.
2 Step 1: Clarify Goals and Break Down Tasks
| Subtask | Description | Success Criteria |
|---|---|---|
① Get execution | Dynamic hidden field on the CAS login page | Printed value like e1s1... |
| ② Obtain public key | /cas/v2/getPubKey returns modulus, exponent | Can be parsed as large integers |
| ③ Replicate front-end encryption | security.js → encryptedString() | Consistent with browser output |
| ④ Submit form | Contains account, encrypted password, execution | Server returns 302, Location carries ticket= |
| ⑤ Follow the ticket | .../fromcas?ticket=ST-... | Server Set-Cookie issues AUTHORIZATION= |
| ⑥ Print Cookie | sess.cookies.get("AUTHORIZATION") | Terminal outputs a 32-byte hex string |
3 Step 2: Packet Capture & Restore Login Flow
- Tools: Browser DevTools (Network), Fiddler, Charles, etc.
- Steps Overview
- Visit
https://auth.example.edu.cn/cas/login?service=https://learning.example.edu.cn/api/fromcas. - Fill in account and password and submit, returns 302, Location with
ticket=ST-.... The browser automatically GETs that ticket URL; the server responds with another 302 to the homepage while issuing the business domain Cookie:
Set-Cookie: AUTHORIZATION=3EE2248EABJH8CBB920881E67729341A; Domain=.example.edu.cn; Path=/
- Visit
- Key Findings
executionchanges on every refresh; old values prompt "Page idle timeout".- The password is not in plaintext nor Base64, but encrypted via RSA + hexadecimal space-separated chunks.
4 Step 3: Identify Dynamic Parameter execution
Searching for execution in the login page source reveals something like:
<input type="hidden" name="execution" value="e3s1" />
In the script, extract it using regex or BeautifulSoup:
html = sess.get(LOGIN_PAGE).text
execution = re.search(r'name="execution" value="(.*?)"', html).group(1)
5 Step 4: Deep Dive security.js —— Reverse RSA Encryption
The core logic on the front-end (simplified) is as follows:
var reversed = password.split('').reverse().join('');
var key = RSAUtils.getKeyPair(exp, '', mod);
var cipher = RSAUtils.encryptedString(key, reversed);
- Reversal: The entire plaintext is reversed in order.
- Little‑Endian:
encryptedString()concatenates two bytes into one digit, with the low byte first. - Output: Each cipher chunk is converted to lowercase hex, separated by spaces; no Base64 or PKCS#1 padding.
6 Step 5: Replicate Front-End Encryption in Python
import math
def js_style_rsa(pwd, mod_hex, exp_hex):
rev = pwd[::-1].encode()
n, e = int(mod_hex, 16), int(exp_hex, 16)
chunk = 2 * (math.ceil(n.bit_length()/16) - 1)
buf = bytearray(rev)
while len(buf) % chunk:
buf.append(0)
return ' '.join(
format(pow(int.from_bytes(buf[i:i+chunk], 'little'), e, n), 'x')
for i in range(0, len(buf), chunk)
)
7 Step 6: Assemble the First POST
form = {
'username': user,
'password': js_style_rsa(pwd, pub['modulus'], pub['exponent']),
'authcode': '',
'execution': execution,
'_eventId': 'submit'
}
resp = sess.post(LOGIN_PAGE, data=form, allow_redirects=False)
Success indicator: resp.status_code == 302 and Location contains ticket=.
8 Step 7: Follow the 302, Obtain the Business Cookie
redirect_url = resp.headers['Location']
sess.get(redirect_url, allow_redirects=True)
auth = sess.cookies.get('AUTHORIZATION')
print('AUTHORIZATION =', auth)
9 Step 8: Write the Complete Script
Complete script (desensitized):
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""cas_login.py —— Generic CAS login script example (domain desensitized)"""
import requests, re, math
from getpass import getpass
LOGIN_PAGE = (
'https://auth.example.edu.cn/cas/login'
'?service=https://learning.example.edu.cn/api/fromcas'
)
PUBKEY_API = 'https://auth.example.edu.cn/cas/v2/getPubKey'
def js_style_rsa(pwd, mod, exp):
rev = pwd[::-1].encode()
n, e = int(mod, 16), int(exp, 16)
chunk = 2 * (math.ceil(n.bit_length()/16) - 1)
buf = bytearray(rev)
while len(buf) % chunk:
buf.append(0)
return ' '.join(format(pow(int.from_bytes(buf[i:i+chunk], 'little'), e, n), 'x')
for i in range(0, len(buf), chunk))
def main():
user = input('Student ID / Username: ').strip()
pwd = getpass('Password (input will not be echoed): ')
s = requests.Session()
s.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
html = s.get(LOGIN_PAGE).text
execution = re.search(r'name="execution" value="(.*?)"', html).group(1)
pub = s.get(PUBKEY_API).json()
enc = js_style_rsa(pwd, pub['modulus'], pub['exponent'])
data = {'username': user, 'password': enc, 'authcode': '',
'execution': execution, '_eventId': 'submit'}
r = s.post(LOGIN_PAGE, data=data, allow_redirects=False)
if 'Location' not in r.headers:
print('Login failed')
return
s.get(r.headers['Location'], allow_redirects=True)
print('✅ AUTHORIZATION =', s.cookies.get('AUTHORIZATION'))
if __name__ == '__main__':
main()
10 Step 9: Common Pitfalls & Debugging Tips
| Symptom | Cause | Solution |
|---|---|---|
| Prompts "Page idle timeout" | execution expired | Always GET the login page first, then submit immediately |
| Returns 200 instead of 302 | Password encryption mismatch | Compare with browser ciphertext; pay attention to reversal & Little‑Endian |
AUTHORIZATION=None | Not accessing ticket URL / ticket expired | Follow redirect or re-login |
| CAPTCHA appears | Too many failed attempts triggering risk control | Manual input or use CAPTCHA solving service |
11 Conclusion: Possible Extensions
- Scheduled tasks: Use cron or Windows Task Scheduler to run the script periodically.
- Batch download materials: Crawl course file list endpoints + loop download.
- Email/notification: Detect new announcements and push via SMTP.
- GUI tool: Wrap with PyQt or Tkinter to share with classmates.
Disclaimer
All techniques in this article are for personal learning and research only. The domain names and cookies are fictitious and do not correspond to any real system. Do not use the script for unauthorized batch access; otherwise, you assume all consequences.