├── requirements.txt ├── LICENSE ├── README.md ├── .gitignore └── authen.py /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2023.7.22 2 | chardet==5.2.0 3 | charset-normalizer==3.2.0 4 | idna==3.4 5 | requests==2.31.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Patthawee Chumpuvorn, Preeti Yuankrathok 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto-authen-KMITL 2 | 3 | A **_Python script_** that let you automatically authenticate into KMITL network 4 | 5 | 6 | | :warning: **Warning:** This project is only an experiment on KMITL authentication system and it does not provided a bypass for login system | 7 | | --- | 8 | 9 | ## Getting started 10 | ### Prerequisites 11 | * Python 3.x 12 | * Git 13 | * The static IP on your device must be configured. 14 | 15 | ### Installation 16 | 1. Clone repo 17 | ``` 18 | git clone https://github.com/CE-HOUSE/auto-authen-kmitl.git 19 | ``` 20 | 2. `cd` into project directory 21 | ``` 22 | cd auto-authen-kmitl 23 | ``` 24 | 3. Install python packages 25 | ``` 26 | pip install -r requirements.txt 27 | ``` 28 | 29 | ### Usage 30 | 31 | ``` 32 | python3 authen.py 33 | ``` 34 | 35 | For more information 36 | ``` 37 | python3 authen.py --help 38 | ``` 39 | 40 | 41 | 42 | ### Config 43 | | Alias | Name | Description | 44 | |:-----:|:----:|-------------| 45 | | `username` | Username | Username to login _(without **@kmitl.ac.th**)_ | 46 | | `password` | Password | Password to login | 47 | | `ipAddress` | ipAddress | Your public IP | 48 | 49 | ## Credit 50 | * **_assazzin & CSAG_** for original [Auto-authen-KMITL](https://github.com/assazzin/Auto-authen-KMITL) written in Perl language 51 | * **_Network Laboratory_** for original [Auto-authen-KMITL](https://gitlab.com/networklab-kmitl/auto-authen-kmitl) written in Python language Before CSC-KMITL Upgrade Authen Website 52 | * **_ISAG Laboratory_** for nice space to improve our productivity! 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Custom ### 2 | config.json 3 | 4 | 5 | # Created by https://www.gitignore.io/api/python,macos,windows,pycharm,visualstudiocode 6 | 7 | ### macOS ### 8 | # General 9 | .DS_Store 10 | .AppleDouble 11 | .LSOverride 12 | 13 | # Icon must end with two \r 14 | Icon 15 | 16 | # Thumbnails 17 | ._* 18 | 19 | # Files that might appear in the root of a volume 20 | .DocumentRevisions-V100 21 | .fseventsd 22 | .Spotlight-V100 23 | .TemporaryItems 24 | .Trashes 25 | .VolumeIcon.icns 26 | .com.apple.timemachine.donotpresent 27 | 28 | # Directories potentially created on remote AFP share 29 | .AppleDB 30 | .AppleDesktop 31 | Network Trash Folder 32 | Temporary Items 33 | .apdisk 34 | 35 | ### PyCharm ### 36 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 37 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 38 | 39 | .idea 40 | 41 | # # User-specific stuff 42 | # .idea/**/workspace.xml 43 | # .idea/**/tasks.xml 44 | # .idea/**/usage.statistics.xml 45 | # .idea/**/dictionaries 46 | # .idea/**/shelf 47 | 48 | # # Generated files 49 | # .idea/**/contentModel.xml 50 | 51 | # # Sensitive or high-churn files 52 | # .idea/**/dataSources/ 53 | # .idea/**/dataSources.ids 54 | # .idea/**/dataSources.local.xml 55 | # .idea/**/sqlDataSources.xml 56 | # .idea/**/dynamic.xml 57 | # .idea/**/uiDesigner.xml 58 | # .idea/**/dbnavigator.xml 59 | 60 | # # Gradle 61 | # .idea/**/gradle.xml 62 | # .idea/**/libraries 63 | 64 | # Gradle and Maven with auto-import 65 | # When using Gradle or Maven with auto-import, you should exclude module files, 66 | # since they will be recreated, and may cause churn. Uncomment if using 67 | # auto-import. 68 | # .idea/modules.xml 69 | # .idea/*.iml 70 | # .idea/modules 71 | 72 | # CMake 73 | cmake-build-*/ 74 | 75 | # Mongo Explorer plugin 76 | # .idea/**/mongoSettings.xml 77 | 78 | # File-based project format 79 | *.iws 80 | 81 | # IntelliJ 82 | out/ 83 | 84 | # mpeltonen/sbt-idea plugin 85 | .idea_modules/ 86 | 87 | # JIRA plugin 88 | atlassian-ide-plugin.xml 89 | 90 | # Cursive Clojure plugin 91 | # .idea/replstate.xml 92 | 93 | # Crashlytics plugin (for Android Studio and IntelliJ) 94 | com_crashlytics_export_strings.xml 95 | crashlytics.properties 96 | crashlytics-build.properties 97 | fabric.properties 98 | 99 | # Editor-based Rest Client 100 | # .idea/httpRequests 101 | 102 | ### PyCharm Patch ### 103 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 104 | 105 | # *.iml 106 | # modules.xml 107 | # .idea/misc.xml 108 | # *.ipr 109 | 110 | # Sonarlint plugin 111 | # .idea/sonarlint 112 | 113 | ### Python ### 114 | # Byte-compiled / optimized / DLL files 115 | __pycache__/ 116 | *.py[cod] 117 | *$py.class 118 | 119 | # C extensions 120 | *.so 121 | 122 | # Distribution / packaging 123 | .Python 124 | build/ 125 | develop-eggs/ 126 | dist/ 127 | downloads/ 128 | eggs/ 129 | .eggs/ 130 | lib/ 131 | lib64/ 132 | parts/ 133 | sdist/ 134 | var/ 135 | wheels/ 136 | *.egg-info/ 137 | .installed.cfg 138 | *.egg 139 | MANIFEST 140 | 141 | # PyInstaller 142 | # Usually these files are written by a python script from a template 143 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 144 | *.manifest 145 | *.spec 146 | 147 | # Installer logs 148 | pip-log.txt 149 | pip-delete-this-directory.txt 150 | 151 | # Unit test / coverage reports 152 | htmlcov/ 153 | .tox/ 154 | .coverage 155 | .coverage.* 156 | .cache 157 | nosetests.xml 158 | coverage.xml 159 | *.cover 160 | .hypothesis/ 161 | .pytest_cache/ 162 | 163 | # Translations 164 | *.mo 165 | *.pot 166 | 167 | # Django stuff: 168 | *.log 169 | local_settings.py 170 | db.sqlite3 171 | 172 | # Flask stuff: 173 | instance/ 174 | .webassets-cache 175 | 176 | # Scrapy stuff: 177 | .scrapy 178 | 179 | # Sphinx documentation 180 | docs/_build/ 181 | 182 | # PyBuilder 183 | target/ 184 | 185 | # Jupyter Notebook 186 | .ipynb_checkpoints 187 | 188 | # IPython 189 | profile_default/ 190 | ipython_config.py 191 | 192 | # pyenv 193 | .python-version 194 | 195 | # celery beat schedule file 196 | celerybeat-schedule 197 | 198 | # SageMath parsed files 199 | *.sage.py 200 | 201 | # Environments 202 | .env 203 | .venv 204 | env/ 205 | venv/ 206 | ENV/ 207 | env.bak/ 208 | venv.bak/ 209 | 210 | # Spyder project settings 211 | .spyderproject 212 | .spyproject 213 | 214 | # Rope project settings 215 | .ropeproject 216 | 217 | # mkdocs documentation 218 | /site 219 | 220 | # mypy 221 | .mypy_cache/ 222 | .dmypy.json 223 | dmypy.json 224 | 225 | ### Python Patch ### 226 | .venv/ 227 | 228 | ### Python.VirtualEnv Stack ### 229 | # Virtualenv 230 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 231 | [Bb]in 232 | [Ii]nclude 233 | [Ll]ib 234 | [Ll]ib64 235 | [Ll]ocal 236 | [Ss]cripts 237 | pyvenv.cfg 238 | pip-selfcheck.json 239 | 240 | ### VisualStudioCode ### 241 | .vscode 242 | # !.vscode/settings.json 243 | # !.vscode/tasks.json 244 | # !.vscode/launch.json 245 | # !.vscode/extensions.json 246 | 247 | ### Windows ### 248 | # Windows thumbnail cache files 249 | Thumbs.db 250 | ehthumbs.db 251 | ehthumbs_vista.db 252 | 253 | # Dump file 254 | *.stackdump 255 | 256 | # Folder config file 257 | [Dd]esktop.ini 258 | 259 | # Recycle Bin used on file shares 260 | $RECYCLE.BIN/ 261 | 262 | # Windows Installer files 263 | *.cab 264 | *.msi 265 | *.msix 266 | *.msm 267 | *.msp 268 | 269 | # Windows shortcuts 270 | *.lnk 271 | 272 | 273 | # End of https://www.gitignore.io/api/python,macos,windows,pycharm,visualstudiocode 274 | -------------------------------------------------------------------------------- /authen.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import argparse 3 | import json 4 | import shutil 5 | import getpass 6 | import signal 7 | import time 8 | import requests 9 | import sys 10 | import os 11 | 12 | username = '' 13 | password = '' 14 | ipAddress= '' 15 | acip="10.252.13.10" 16 | umac = ''.join(['{:02x}'.format((uuid.getnode() >> ele) & 0xff) for ele in range(0,8*6,8)][::-1]) 17 | time_repeat = 5*60 18 | max_login_attempt = 20 19 | 20 | client_ip = '' 21 | server_url = 'https://portal.kmitl.ac.th:19008/portalauth/login' 22 | server_url_heartbeat = 'https://nani.csc.kmitl.ac.th/network-api/data/' 23 | data = '' 24 | agent = requests.session() 25 | 26 | 27 | # handle Ctrl+C 28 | def signal_handler(signal, frame): 29 | print_format('Good bye!', end='\n') 30 | sys.exit(0) 31 | 32 | 33 | signal.signal(signal.SIGINT, signal_handler) 34 | 35 | 36 | # determine terminal size 37 | large_terminal = True 38 | column, line = shutil.get_terminal_size() 39 | if column < 108: 40 | large_terminal = False 41 | 42 | 43 | # arguments parser 44 | parser = argparse.ArgumentParser( 45 | description='Automatically authenticate into KMITL network system.') 46 | parser.add_argument('-u', '--username', dest='username', 47 | help='username without @kmitl.ac.th (usually student ID)') 48 | parser.add_argument('-p', '--password', dest='password', help='password') 49 | parser.add_argument('-ip', '--IpAddress', dest='ipAddress', help='ipAddress') 50 | parser.add_argument('-i', '--interval', dest='interval', type=int, 51 | help='heartbeat interval in second (default: {} seconds)'.format(time_repeat)) 52 | parser.add_argument('--max-login-attempt', dest='max_attempt', type=int, 53 | help='maximum login attempt (default: {} times)'.format(max_login_attempt)) 54 | parser.add_argument('--config', dest='config', action='store_const', 55 | const=True, default=False, help='create config file') 56 | 57 | def print_format(*args, large_only=False, small_only=False, show_time=True, end='\n\n', **kwargs): 58 | if (large_only and not large_terminal) or (small_only and large_terminal): 59 | return 60 | 61 | if large_terminal: 62 | print('\t', end='') 63 | if show_time: 64 | print(time.asctime(time.localtime()), '[x]', end=' ') 65 | print(*args, **kwargs, end=end) 66 | 67 | 68 | def print_error(*args, **kwargs): 69 | print_format(*args, **kwargs, end='\n') 70 | #sys.exit(1) 71 | 72 | 73 | def init(): 74 | logo = ''' 75 | ██████╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗███████╗███████╗ 76 | ██╔════╝██╔════╝ ██║ ██║██╔═══██╗██║ ██║██╔════╝██╔════╝ 77 | ██║ █████╗ ███████║██║ ██║██║ ██║███████╗█████╗ 78 | ██║ ██╔══╝ ██╔══██║██║ ██║██║ ██║╚════██║██╔══╝ 79 | ╚██████╗███████╗ ██║ ██║╚██████╔╝╚██████╔╝███████║███████╗ 80 | ╚═════╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ 81 | 82 | https://github.com/CE-HOUSE 83 | 84 | ''' 85 | 86 | print_format(logo, large_only=True, show_time=False) 87 | print_format('CE-HOUSE', show_time=True) 88 | 89 | def login(): 90 | global data 91 | try: 92 | url = server_url 93 | content = agent.post(url,params={'userName': username, 'userPass': password,'uaddress': ipAddress,'umac':umac,'agreed':1,'acip':acip,'authType':1}) 94 | except requests.exceptions.RequestException: 95 | print_format('Connection lost...' ,show_time=True, end='\n') 96 | return 97 | content_dict = json.loads(content.text) 98 | 99 | # check if request is successful 100 | data = content_dict['data'] 101 | 102 | if content.status_code != 200: 103 | print_format('Error! Something went wrong (maybe wrong username and/or password?)...') 104 | 105 | def heatbeat() -> (bool, bool): 106 | try: 107 | url = server_url 108 | content = agent.post(server_url_heartbeat,params={'username': username,'os':"Chrome v116.0.5845.141 on Windows 10 64-bit","speed":1.29,"newauth":1}) 109 | except requests.exceptions.RequestException: 110 | print_format('Connection lost...') 111 | time.sleep(1) 112 | return False, False 113 | status = content.status_code 114 | if status == 200: 115 | print_format('Heatbeat OK...') 116 | return True, True 117 | else: 118 | print_format('Heatbeat failed...') 119 | return True, False 120 | 121 | 122 | def checkConnection() -> (bool, bool): 123 | try: 124 | content = requests.get('http://detectportal.firefox.com/success.txt') 125 | except requests.exceptions.RequestException: 126 | return False, False 127 | if content.text == 'success\n': 128 | return True, True 129 | return True, False 130 | 131 | def start(): 132 | login_attempt = 0 133 | printed_logged_in = False 134 | printed_lost = False 135 | reset_timer = time.time()+(8*60*60) 136 | login() 137 | while True: 138 | remain_to_reset = reset_timer-time.time() 139 | connection, internet = checkConnection() 140 | if remain_to_reset <= 480: 141 | return 142 | if(connection and internet): 143 | if not printed_logged_in: # print only when log in successful 144 | print('',end='\n') 145 | print_format('Welcome {}!'.format(username), end='\n') 146 | print_format('Your IP:', ipAddress, end='\n') 147 | print_format('Heartbeat every', time_repeat, 'seconds', end='\n') 148 | print_format('Max login attempt:',max_login_attempt) 149 | print_format(''' 150 | ██████╗ ██████╗ ███╗ ██╗███╗ ██╗███████╗ ██████╗████████╗███████╗██████╗ 151 | ██╔════╝██╔═══██╗████╗ ██║████╗ ██║██╔════╝██╔════╝╚══██╔══╝██╔════╝██╔══██╗ 152 | ██║ ██║ ██║██╔██╗ ██║██╔██╗ ██║█████╗ ██║ ██║ █████╗ ██║ ██║ 153 | ██║ ██║ ██║██║╚██╗██║██║╚██╗██║██╔══╝ ██║ ██║ ██╔══╝ ██║ ██║ 154 | ╚██████╗╚██████╔╝██║ ╚████║██║ ╚████║███████╗╚██████╗ ██║ ███████╗██████╔╝ 155 | ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═════╝ 156 | ''', show_time=False) 157 | printed_logged_in = True 158 | printed_lost = False 159 | time.sleep(time_repeat) 160 | connection, heatdone = heatbeat() 161 | if not connection and not heatdone: 162 | return 163 | elif connection and not heatdone: 164 | login() 165 | elif connection and not internet: 166 | if login_attempt == max_login_attempt: 167 | print_error( 168 | 'Error! Please recheck your username and password...') 169 | login() 170 | login_attempt += 1 171 | else: 172 | if not printed_lost: 173 | print('',end='\n') 174 | print_format(''' 175 | ██╗ ██████╗ ███████╗████████╗ 176 | ██║ ██╔═══██╗██╔════╝╚══██╔══╝ 177 | ██║ ██║ ██║███████╗ ██║ 178 | ██║ ██║ ██║╚════██║ ██║ 179 | ███████╗╚██████╔╝███████║ ██║ 180 | ╚══════╝ ╚═════╝ ╚══════╝ ╚═╝ 181 | ''', show_time=False) 182 | printed_lost = True 183 | printed_logged_in = False 184 | if login_attempt > max_login_attempt: 185 | print_error("Login attempt exceed maximum") 186 | break; 187 | login() 188 | login_attempt+=1 189 | time.sleep(10) 190 | 191 | 192 | def create_config(): 193 | input_username = input('Your username (mostly student ID, without @kmitl.ac.th): ') 194 | input_password = getpass.getpass('Your password: ') 195 | input_yourIp = input('Your Public IP Address: ') 196 | 197 | data = {} 198 | if input_username != '': 199 | data.update({'username': input_username}) 200 | if input_password != '': 201 | data.update({'password': input_password}) 202 | if input_yourIp != '': 203 | data.update({'ipAddress': input_yourIp}) 204 | 205 | with open('config.json', 'w') as config_file: 206 | json.dump(data, config_file, indent=4) 207 | 208 | if __name__ == '__main__': 209 | # get arguments 210 | args = parser.parse_args() 211 | if args.config: 212 | create_config() 213 | 214 | if not os.path.isfile('config.json'): 215 | try: 216 | to_create = input( 217 | 'Cannot found \'config.json\', do you want to create a new one? (Y/n): ').lower() 218 | if to_create not in ['n', 'no']: 219 | create_config() 220 | except EOFError: 221 | print('\nGood bye!') 222 | sys.exit(0) 223 | 224 | try: 225 | with open('config.json', 'r') as config_file: 226 | config = json.load(config_file) 227 | if 'username' in config: 228 | username = config['username'] 229 | if 'password' in config: 230 | password = config['password'] 231 | if 'ipAddress' in config: 232 | ipAddress = config['ipAddress'] 233 | except FileNotFoundError: 234 | pass 235 | 236 | # get username and password from args 237 | if args.username is not None: 238 | username = args.username 239 | if args.password is not None: 240 | password = args.password 241 | 242 | # get heartbeat interval from args 243 | if args.ipAddress is not None: 244 | ipAddress = args.ipAddress 245 | 246 | # get maximum login attempt from args 247 | if args.max_attempt is not None: 248 | max_login_attempt = args.max_attempt 249 | 250 | # check if username and password are provided 251 | if username == '' or password == '' or username is None or password is None or ipAddress == '' or ipAddress is None: 252 | print('Error! Please provide username and password...') 253 | sys.exit(1) 254 | 255 | init() 256 | 257 | print_format('Logging in with username \'{}\'...'.format(username)) 258 | 259 | while True: 260 | start() --------------------------------------------------------------------------------