├── image-haom.png ├── test.py ├── README.md └── cytls └── cytls.py /image-haom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2833844911/passja3/HEAD/image-haom.png -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from cytls import cytls 2 | 3 | headers ={ 4 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", 5 | } 6 | 7 | 8 | proxies = {"https": "http://14.121.105.170:8008"} 9 | 10 | url ="https://www.baidu.com/" 11 | req = cytls.get(url, headers=headers, proxies=proxies) 12 | 13 | 14 | dt = req.text 15 | print(req.status_code) 16 | print(req.headers) 17 | print(dt) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect TLS forwarding calls break through the JA3 check 2 | 3 | 4 | Use the same as requests. 5 | The project fully emulates the TLS fingerprint of the browser. 6 | 7 | ##### Test examples (CloudFlare) 8 | 9 | ```python 10 | from cytls import cytls 11 | 12 | headers = { 13 | "sec-ch-ua": '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"', 14 | "accept-language": 'en-US,en;q=0.9', 15 | "upgrade-insecure-requests": "1", 16 | "sec-ch-ua-mobile": "?0", 17 | "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', 18 | "accept": 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 19 | "priority": "u=0, i", 20 | "sec-ch-ua-platform": '"Windows"', 21 | "Sec-Fetch-Site": "same-origin", 22 | "Sec-Fetch-Mode": "cors", 23 | "Sec-Fetch-Dest": "empty", 24 | "Accept-Encoding": "gzip, deflate, br, zstd", 25 | } 26 | proxies = {"https": "http://user:passwd@255.255.255:8888"} 27 | 28 | url = "https://www.utmel.com/productdetail/texasinstruments-tps63802dlar-7758755" 29 | 30 | req = cytls.get(url, headers=headers, proxies=proxies, allow_redirects=True) 31 | 32 | 33 | dt = req.text 34 | print(req.status_code) 35 | print(req.headers) 36 | print(dt) 37 | 38 | 39 | ``` 40 | 41 | outcome 42 | 43 | ![image-haom.png](./image-haom.png) 44 | 45 | 46 | Contact email 2833844911@qq.com 47 | 48 | 49 | # Perfect TLS forwarding calls break through the JA3 check 50 | 51 | 52 | Use the same as requests. 53 | The project fully emulates the TLS fingerprint of the browser. 54 | 55 | ##### Test examples (CloudFlare) 56 | 57 | ```python 58 | from cytls import cytls 59 | 60 | headers = { 61 | "sec-ch-ua": '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"', 62 | "accept-language": 'en-US,en;q=0.9', 63 | "upgrade-insecure-requests": "1", 64 | "sec-ch-ua-mobile": "?0", 65 | "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', 66 | "accept": 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 67 | "priority": "u=0, i", 68 | "sec-ch-ua-platform": '"Windows"', 69 | "Sec-Fetch-Site": "same-origin", 70 | "Sec-Fetch-Mode": "cors", 71 | "Sec-Fetch-Dest": "empty", 72 | "Accept-Encoding": "gzip, deflate, br, zstd", 73 | } 74 | proxies = {"https": "http://user:passwd@255.255.255:8888"} 75 | 76 | url = "https://www.utmel.com/productdetail/texasinstruments-tps63802dlar-7758755" 77 | 78 | req = cytls.get(url, headers=headers, proxies=proxies, allow_redirects=True) 79 | 80 | 81 | dt = req.text 82 | print(req.status_code) 83 | print(req.headers) 84 | print(dt) 85 | 86 | 87 | ``` 88 | 89 | outcome 90 | 91 | ![image-haom.png](./image-haom.png) 92 | 93 | 94 | Contact email 2833844911@qq.com 95 | 96 | 97 | 98 | ### cronet Browser Request Library Local Forwarding to Bypass JA3 99 | 100 | #### Installing Docker on a CentOS System Can Be Done as Follows: 101 | 102 | 1. Uninstalling Old Versions of Docker 103 | 104 | First, if there are old versions of Docker installed on your system, you need to uninstall them. Older versions might be referred to as docker or docker-engine. Use the following commands to uninstall: 105 | 106 | ```bash 107 | sudo yum remove docker \ 108 | docker-client \ 109 | docker-client-latest \ 110 | docker-common \ 111 | docker-latest \ 112 | docker-latest-logrotate \ 113 | docker-logrotate \ 114 | docker-engine 115 | ``` 116 | 2. Setting Up the Docker Repository 117 | 118 | Install necessary packages that will allow yum to use packages from HTTPS sources: 119 | 120 | 121 | ```bash 122 | sudo yum install -y yum-utils 123 | ``` 124 | 125 | Next, set up the stable repository: 126 | 127 | 128 | ```bash 129 | sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 130 | 131 | sudo yum update -y && sudo yum install -y docker-ce docker-ce-cli containerd.io 132 | ``` 133 | 134 | 3. Starting Docker 135 | 136 | After installation, start the Docker service: 137 | 138 | 139 | ```bash 140 | sudo systemctl start docker 141 | ``` 142 | 4. Download the Image (cytlszf.tar) 143 | Link:https://pan.baidu.com/s/1DuhWBGCjTbvDEFZFjVLWpQ 144 | Access Code:0tbc 145 | OR 146 | https://drive.google.com/drive/folders/1aZS514ROqit3dW3c8Db7566y4cuBUkL6?usp=sharing 147 | 148 | 6. Loading the Image 149 | 150 | 151 | ```bash 152 | docker load -i cytlszf.tar 153 | ``` 154 | 6. Starting the Container (Opening Port 28080 on the Server) 155 | 156 | 157 | ```bash 158 | docker run -p 28080:8081 -d cytlszf:latest 159 | ``` 160 | 7.Download the passja3 Project 161 | 162 | Link: [passja3](https://github.com/2833844911/passja3 "to") 163 | 8. Replace the Top Link of cytls.py with Your Own 164 | Replace 165 | ud = "http://103.71.69.97:28080/request" 166 | with 167 | ud = "http://yourip:28080/request" 168 | 9. Normal Usage of the passja3 Project 169 | 170 | can see 171 | https://github.com/wmm1996528/cronet-go/tree/main/examples 172 | -------------------------------------------------------------------------------- /cytls/cytls.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json as jn 3 | from urllib.parse import urlencode, urljoin 4 | import urllib 5 | from types import MethodType 6 | import http.cookies 7 | 8 | ud = "http://103.71.69.63:28080/request" 9 | 10 | def get(url, headers=None, proxies=None, params=None, cookies=None, timeout=40, http2=False, allow_redirects=True): 11 | if params != None: 12 | url = url.split("?")[0] 13 | url = f'{url}?{urlencode(params)}' 14 | 15 | if headers == None: 16 | headers = {} 17 | if cookies != None: 18 | headers["Cookie"] = '; '.join([f'{k}={v}' for k, v in cookies.items()]) 19 | for k,v in headers.items(): 20 | if v == None or v == '': 21 | headers[k] = ' ' 22 | 23 | if proxies == None: 24 | print("Please bring your proxy IP address!!!") 25 | return 26 | 27 | if "http" in proxies: 28 | proxies = proxies['http'].split('/')[-1].strip() 29 | if "https" in proxies: 30 | proxies = proxies['https'].split('/')[-1].strip() 31 | 32 | # if allow_redirects == True: 33 | # cancdx = "1" 34 | # else: 35 | # cancdx = "0" 36 | dt = { 37 | "cancdx": "0", 38 | "method": "GET", 39 | "url": url, 40 | "headers": headers, 41 | "timeout": timeout, 42 | "postData": "", 43 | "proxy": "http://"+proxies, 44 | "http2": http2 45 | } 46 | 47 | response = requests.post(ud, json=dt, timeout=timeout, allow_redirects=False) 48 | response.url = url 49 | set_cookies = response.headers.get('Set-Cookie') 50 | if set_cookies: 51 | parsed_cookies = http.cookies.SimpleCookie() 52 | # 将 set_cookies 字符串拆分为单个 Cookie 53 | cookies_list = set_cookies.split(', ') 54 | 55 | # 逐个加载每个 Cookie 56 | for cookie_str in cookies_list: 57 | parsed_cookies.load(cookie_str) 58 | # parsed_cookies.load(set_cookies) 59 | response.cookies = parsed_cookies 60 | cookies_dict = {key: morsel.value for key, morsel in response.cookies.items()} 61 | cookies.update(cookies_dict) 62 | if allow_redirects == True: 63 | if response.status_code == 303: 64 | return get(urljoin(url,response.headers['Location']), headers, proxies, params, cookies, timeout, http2, allow_redirects) 65 | elif response.status_code == 302: 66 | return get(urljoin(url,response.headers['Location']), headers, proxies, None, cookies, timeout, http2, allow_redirects) 67 | elif response.status_code == 301: 68 | return get(urljoin(url,response.headers['Location']), headers, proxies, None, cookies, timeout, http2, allow_redirects) 69 | 70 | return response 71 | 72 | 73 | def post(url, headers=None, proxies=None, params=None, cookies=None, json=None, data=None, timeout=40, http2=False,allow_redirects=True): 74 | if params != None: 75 | url = url.split("?")[0] 76 | url = f'{url}?{urlencode(params)}' 77 | 78 | if headers == None: 79 | headers = {} 80 | if cookies != None: 81 | headers["Cookie"] = '; '.join([f'{k}={v}' for k, v in cookies.items()]) 82 | for k,v in headers.items(): 83 | if v == None or v == '': 84 | headers[k] = ' ' 85 | 86 | if type(data) == type({}): 87 | data = urllib.parse.urlencode(data) 88 | 89 | if proxies == None: 90 | print("Please bring your proxy IP address!!!") 91 | return 92 | 93 | if "http" in proxies: 94 | proxies = proxies['http'].split('/')[-1].strip() 95 | if "https" in proxies: 96 | proxies = proxies['https'].split('/')[-1].strip() 97 | 98 | postData = "" 99 | if data != None: 100 | postData = data 101 | 102 | headers['Content-Type'] = 'application/x-www-form-urlencoded' 103 | if json != None: 104 | postData = jn.dumps(json) 105 | headers['Content-Type'] = 'application/json' 106 | 107 | # if allow_redirects == True: 108 | # cancdx = "1" 109 | # else: 110 | # cancdx = "0" 111 | dt = { 112 | "cancdx": "0", 113 | "method": "POST", 114 | "url": url, 115 | "headers": headers, 116 | "postData": postData, 117 | "timeout": timeout, 118 | "proxy": "http://"+proxies, 119 | "http2": http2 120 | } 121 | response = requests.post(ud, json=dt, timeout=timeout, allow_redirects=False) 122 | set_cookies = response.headers.get('Set-Cookie') 123 | if set_cookies: 124 | parsed_cookies = http.cookies.SimpleCookie() 125 | # 将 set_cookies 字符串拆分为单个 Cookie 126 | cookies_list = set_cookies.split(', ') 127 | 128 | # 逐个加载每个 Cookie 129 | for cookie_str in cookies_list: 130 | parsed_cookies.load(cookie_str) 131 | 132 | response.cookies = parsed_cookies 133 | cookies_dict = {key: morsel.value for key, morsel in response.cookies.items()} 134 | cookies.update(cookies_dict) 135 | response.url = url 136 | if allow_redirects == True: 137 | if response.status_code == 303: 138 | return post(urljoin(url,response.headers['Location']),headers, proxies, None, cookies, json, data, timeout, http2,allow_redirects) 139 | elif response.status_code == 302: 140 | return get(urljoin(url,response.headers['Location']), headers, proxies, None, cookies, timeout, http2, allow_redirects) 141 | elif response.status_code == 301: 142 | return get(urljoin(url,response.headers['Location']), headers, proxies, None, cookies, timeout, http2, allow_redirects) 143 | 144 | return response 145 | 146 | 147 | class Session: 148 | def __init__(self): 149 | self.cookies = {} 150 | self.s = requests.session() 151 | 152 | 153 | def addCookie(self): 154 | pass 155 | 156 | def get(self,url, headers=None, proxies=None, params=None, cookies=None, timeout=40, http2=False, allow_redirects=True,verify=False): 157 | if cookies: 158 | self.cookies.update(cookies) 159 | response = get(url, headers, proxies, params, self.cookies, timeout, http2, allow_redirects) 160 | self.cookies.update(response.cookies) 161 | set_cookies = response.headers.get('Set-Cookie') 162 | if set_cookies: 163 | cookies_dict = {key: morsel.value for key, morsel in response.cookies.items()} 164 | self.cookies.update(cookies_dict) 165 | 166 | parsed_cookies = http.cookies.SimpleCookie() 167 | 168 | # 将字典中的键值对添加到 SimpleCookie 对象中 169 | for key, value in self.cookies.items(): 170 | parsed_cookies[key] = value 171 | response.cookies =parsed_cookies 172 | 173 | def get_cookie(self, key, default=None): 174 | if key in self: 175 | return self[key].value 176 | else: 177 | return default 178 | 179 | # 将新的 get 方法绑定到 response.cookies 180 | response.cookies.get = MethodType(get_cookie, response.cookies) 181 | 182 | return response 183 | 184 | 185 | def post(self,url, headers=None, proxies=None, params=None, cookies=None, json=None, data=None, timeout=40, http2=False,allow_redirects=True,verify=False): 186 | if cookies: 187 | self.cookies.update(cookies) 188 | response = post(url, headers, proxies, params, self.cookies, json, data, timeout, http2,allow_redirects) 189 | set_cookies = response.headers.get('Set-Cookie') 190 | if set_cookies: 191 | cookies_dict = {key: morsel.value for key, morsel in response.cookies.items()} 192 | self.cookies.update(cookies_dict) 193 | parsed_cookies = http.cookies.SimpleCookie() 194 | 195 | # 将字典中的键值对添加到 SimpleCookie 对象中 196 | for key, value in self.cookies.items(): 197 | parsed_cookies[key] = value 198 | response.cookies =parsed_cookies 199 | 200 | # 定义一个新的 get 方法 201 | def get_cookie(self, key, default=None): 202 | if key in self: 203 | return self[key].value 204 | else: 205 | return default 206 | 207 | # 将新的 get 方法绑定到 response.cookies 208 | response.cookies.get = MethodType(get_cookie, response.cookies) 209 | 210 | return response 211 | 212 | 213 | --------------------------------------------------------------------------------