diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6667dbc94c..b9bb7c4968 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -17,6 +17,7 @@ services: restart: always ports: - "80:4000" + - "1337:4001" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: @@ -41,6 +42,8 @@ services: - postgres networks: - mobsf_network + extra_hosts: + - "host.docker.internal:host-gateway" networks: mobsf_network: diff --git a/docker/docker-compose_swarm.yml b/docker/docker-compose_swarm.yml index 9f61faa4b1..0d98aa8a79 100644 --- a/docker/docker-compose_swarm.yml +++ b/docker/docker-compose_swarm.yml @@ -19,6 +19,7 @@ services: restart: always ports: - "8000:8000" + - "1337:1337" volumes: - $HOME/MobSF/mobsf_data:/home/mobsf/.MobSF environment: @@ -34,6 +35,8 @@ services: - postgres networks: - mobsf_network + extra_hosts: + - "host.docker.internal:host-gateway" secrets: - mobsfDB_password - mobsf_api_key diff --git a/docker/nginx.conf b/docker/nginx.conf index 6fcd8598cb..a1f20bb774 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -17,4 +17,18 @@ http { } client_max_body_size 256M; } + server { + listen 4001; + location / { + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port 443; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://mobsf:1337; + proxy_redirect off; + proxy_read_timeout 120; + proxy_buffering on; + } + client_max_body_size 10M; + } } diff --git a/mobsf/DynamicAnalyzer/tools/apk_patcher.py b/mobsf/DynamicAnalyzer/tools/apk_patcher.py index 56ef7ee810..0dd803798b 100644 --- a/mobsf/DynamicAnalyzer/tools/apk_patcher.py +++ b/mobsf/DynamicAnalyzer/tools/apk_patcher.py @@ -79,7 +79,7 @@ def download_frida_gadget(self, frida_arch, aarch, version): return None try: response = requests.get(f'{settings.FRIDA_SERVER}{version}', - timeout=3, + timeout=5, proxies=proxies, verify=verify) for item in response.json()['assets']: @@ -90,6 +90,7 @@ def download_frida_gadget(self, frida_arch, aarch, version): return None logger.info('Downloading frida-gadget %s', fgadget) with requests.get(url, + timeout=5, stream=True, proxies=proxies, verify=verify) as r: diff --git a/mobsf/DynamicAnalyzer/tools/webproxy.py b/mobsf/DynamicAnalyzer/tools/webproxy.py index 278bcee7ee..390c1f31b5 100644 --- a/mobsf/DynamicAnalyzer/tools/webproxy.py +++ b/mobsf/DynamicAnalyzer/tools/webproxy.py @@ -27,8 +27,11 @@ def stop_httptools(url): http_proxy = url.replace('https://', 'http://') headers = {'httptools': 'kill'} url = 'http://127.0.0.1' - requests.get(url, headers=headers, proxies={ - 'http': http_proxy}) + requests.get( + url, + timeout=5, + headers=headers, + proxies={'http': http_proxy}) logger.info('Killing httptools Proxy') except Exception: pass diff --git a/mobsf/DynamicAnalyzer/views/android/frida_server_download.py b/mobsf/DynamicAnalyzer/views/android/frida_server_download.py index d808c05776..3765572fc1 100644 --- a/mobsf/DynamicAnalyzer/views/android/frida_server_download.py +++ b/mobsf/DynamicAnalyzer/views/android/frida_server_download.py @@ -30,13 +30,17 @@ def clean_up_old_binaries(dirc, version): pass -def download_frida_server(url, version, fname): +def download_frida_server(url, version, fname, proxies): """Download frida-server-binary.""" try: download_dir = Path(settings.DWD_DIR) logger.info('Downloading binary %s', fname) dwd_loc = download_dir / fname - with requests.get(url, stream=True) as r: + with requests.get( + url, + timeout=5, + proxies=proxies, + stream=True) as r: with LZMAFile(r.raw) as f: with open(dwd_loc, 'wb') as flip: copyfileobj(f, flip) @@ -62,13 +66,13 @@ def update_frida_server(arch, version): logger.exception('[ERROR] Setting upstream proxy') try: response = requests.get(f'{settings.FRIDA_SERVER}{version}', - timeout=3, + timeout=5, proxies=proxies, verify=verify) for item in response.json()['assets']: if item['name'] == f'{fserver}.xz': url = item['browser_download_url'] - return download_frida_server(url, version, fserver) + return download_frida_server(url, version, fserver, proxies) return False except Exception: logger.exception('[ERROR] Fetching Frida Server Release') diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py index 70790bfaec..e4ce98f8e8 100644 --- a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py @@ -54,6 +54,7 @@ def api_ready(self): """Check API Availability.""" try: r = requests.get(f'{self.api}/ready', + timeout=5, proxies=self.proxies, verify=self.verify) if r.status_code in SUCCESS_RESP: @@ -73,6 +74,7 @@ def api_auth(self): return False r = requests.get( f'{self.api}/projects', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -89,6 +91,7 @@ def get_projects(self): ids = [] r = requests.get( f'{self.api}/projects?ids_only=true', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -104,6 +107,7 @@ def get_authorized_keys(self): """Get SSH public keys associated with a project.""" r = requests.get( f'{self.api}/projects/{self.project_id}/keys', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -124,6 +128,7 @@ def add_authorized_key(self, key): } r = requests.post( f'{self.api}/projects/{self.project_id}/keys', + timeout=5, headers=self.headers, json=data, proxies=self.proxies, @@ -149,6 +154,7 @@ def get_instances(self): instances = [] r = requests.get( f'{self.api}/instances', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -168,6 +174,7 @@ def create_ios_instance(self, name, flavor, version): } r = requests.post( f'{self.api}/instances', + timeout=5, headers=self.headers, json=data, proxies=self.proxies, @@ -182,6 +189,7 @@ class CorelliumModelsAPI(CorelliumInit): def get_models(self): r = requests.get( f'{self.api}/models', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -202,6 +210,7 @@ def get_supported_os(self, model): return False r = requests.get( f'{self.api}/models/{model}/software', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -223,6 +232,7 @@ def start_instance(self): data = {'paused': False} r = requests.post( f'{self.api}/instances/{self.instance_id}/start', + timeout=5, headers=self.headers, json=data, proxies=self.proxies, @@ -238,6 +248,7 @@ def stop_instance(self): data = {'soft': True} r = requests.post( f'{self.api}/instances/{self.instance_id}/stop', + timeout=5, headers=self.headers, json=data, proxies=self.proxies, @@ -252,6 +263,7 @@ def unpause_instance(self): """Unpause instance.""" r = requests.post( f'{self.api}/instances/{self.instance_id}/unpause', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -265,6 +277,7 @@ def reboot_instance(self): """Reboot instance.""" r = requests.post( f'{self.api}/instances/{self.instance_id}/reboot', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -291,6 +304,7 @@ def poll_instance(self): """Check instance status.""" r = requests.get( f'{self.api}/instances/{self.instance_id}', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -306,6 +320,7 @@ def screenshot(self): r = requests.get( (f'{self.api}/instances/{self.instance_id}' '/screenshot.png?scale=1'), + timeout=5, headers=self.headers, stream=True, proxies=self.proxies, @@ -322,6 +337,7 @@ def start_network_capture(self): """Start network capture.""" r = requests.post( f'{self.api}/instances/{self.instance_id}/sslsplit/enable', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -338,6 +354,7 @@ def stop_network_capture(self): """Stop network capture.""" r = requests.post( f'{self.api}/instances/{self.instance_id}/sslsplit/disable', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -351,6 +368,7 @@ def download_network_capture(self): """Download network capture.""" r = requests.get( f'{self.api}/instances/{self.instance_id}/networkMonitor.pcap', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -364,6 +382,7 @@ def console_log(self): """Get Console Log.""" r = requests.get( f'{self.api}/instances/{self.instance_id}/consoleLog', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -377,6 +396,7 @@ def get_ssh_connection_string(self): """Get SSH connection string.""" r = requests.get( f'{self.api}/instances/{self.instance_id}/quickConnectCommand', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -464,6 +484,7 @@ def device_input(self, event, x, y, max_x, max_y): {'buttons': [], 'wait': 100}] r = requests.post( f'{self.api}/instances/{self.instance_id}/input', + timeout=5, headers=self.headers, json=data, proxies=self.proxies, @@ -485,6 +506,7 @@ def agent_ready(self): """Agent ready.""" r = requests.get( f'{self.api}/instances/{self.instance_id}/agent/v1/app/ready', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -500,6 +522,7 @@ def unlock_device(self): """Unlock iOS device.""" r = requests.post( f'{self.api}/instances/{self.instance_id}/agent/v1/system/unlock', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -533,6 +556,7 @@ def install_ipa(self): """Install IPA.""" r = requests.post( f'{self.api}/instances/{self.instance_id}/agent/v1/app/install', + timeout=5, headers=self.headers, json={'path': '/tmp/app.ipa'}, proxies=self.proxies, @@ -548,6 +572,7 @@ def run_app(self, bundle_id): r = requests.post( (f'{self.api}/instances/{self.instance_id}' f'/agent/v1/app/apps/{bundle_id}/run'), + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -562,6 +587,7 @@ def stop_app(self, bundle_id): r = requests.post( (f'{self.api}/instances/{self.instance_id}' f'/agent/v1/app/apps/{bundle_id}/kill'), + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -576,6 +602,7 @@ def remove_app(self, bundle_id): r = requests.post( (f'{self.api}/instances/{self.instance_id}' f'/agent/v1/app/apps/{bundle_id}/uninstall'), + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -589,6 +616,7 @@ def list_apps(self): """List all apps installed.""" r = requests.get( f'{self.api}/instances/{self.instance_id}/agent/v1/app/apps', + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) @@ -603,6 +631,7 @@ def get_icons(self, bundleids): r = requests.get( (f'{self.api}/instances/{self.instance_id}' f'/agent/v1/app/icons?{bundleids}'), + timeout=5, headers=self.headers, proxies=self.proxies, verify=self.verify) diff --git a/mobsf/MalwareAnalyzer/views/VirusTotal.py b/mobsf/MalwareAnalyzer/views/VirusTotal.py index f9b4fa777b..30d75cc16d 100755 --- a/mobsf/MalwareAnalyzer/views/VirusTotal.py +++ b/mobsf/MalwareAnalyzer/views/VirusTotal.py @@ -47,6 +47,7 @@ def get_report(self): try: response = requests.get( url, + timeout=5, params=params, headers=headers, proxies=proxies, @@ -100,6 +101,7 @@ def upload_file(self, file_path): try: response = requests.post( url, + timeout=5, files=files, data=headers, proxies=proxies, diff --git a/mobsf/MobSF/init.py b/mobsf/MobSF/init.py index a83f768b99..0214c642f0 100644 --- a/mobsf/MobSF/init.py +++ b/mobsf/MobSF/init.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) -VERSION = '4.1.6' +VERSION = '4.1.7' BANNER = r""" __ __ _ ____ _____ _ _ _ | \/ | ___ | |__/ ___|| ___|_ _| || | / | diff --git a/mobsf/MobSF/settings.py b/mobsf/MobSF/settings.py index cc1a0885f3..919466346b 100644 --- a/mobsf/MobSF/settings.py +++ b/mobsf/MobSF/settings.py @@ -414,7 +414,6 @@ DOMAIN_MALWARE_SCAN = os.getenv('MOBSF_DOMAIN_MALWARE_SCAN', '1') APKID_ENABLED = os.getenv('MOBSF_APKID_ENABLED', '1') - QUARK_ENABLED = bool(os.getenv('MOBSF_QUARK_ENABLED', '')) # ================================================== # ======WINDOWS STATIC ANALYSIS SETTINGS =========== # Private key diff --git a/mobsf/MobSF/tools_download.py b/mobsf/MobSF/tools_download.py index 35b7de9528..a763a5e0e9 100644 --- a/mobsf/MobSF/tools_download.py +++ b/mobsf/MobSF/tools_download.py @@ -1,4 +1,3 @@ -"""Download tools required by MobSF.""" import logging import sys import shutil @@ -7,14 +6,56 @@ import platform from pathlib import Path from urllib.request import ( + ProxyHandler, Request, - urlopen, + build_opener, + getproxies, ) - +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) +def download_file(url, file_path): + req = Request(url) + system_proxies = getproxies() + proxy_handler = ProxyHandler(system_proxies) + opener = build_opener(proxy_handler) + + with opener.open(req) as response: + if response.status == 200: + file_size = int(response.headers.get('Content-Length', 0)) + downloaded = 0 + block_size = 8192 # 8KB + + with open(file_path, 'wb') as f: + while True: + buffer = response.read(block_size) + if not buffer: + break + downloaded += len(buffer) + f.write(buffer) + + # Print progress + if file_size > 0: + done = int(50 * downloaded / file_size) + fmt = (f'\r[{"#" * done}{"-" * (50-done)}] ' + f'{downloaded * 100 / file_size:.2f}%') + sys.stdout.write(fmt) + sys.stdout.flush() + + if downloaded != file_size: + err = (f'Downloaded file size ({downloaded}) ' + f'does not match expected size ({file_size})') + raise Exception(err) + + return downloaded + else: + raise Exception(f'Failed to download file. Status code: {response.status}') + + def install_jadx(mobsf_home, version='1.5.0'): """Install JADX dynamically.""" try: @@ -34,15 +75,9 @@ def install_jadx(mobsf_home, version='1.5.0'): delete=False, mode='wb', suffix='.zip') as tmp_zip_file: - # Download JADX zip file - with urlopen(Request(url)) as response: - if response.status == 200: - tmp_zip_file.write(response.read()) - logger.info('JADX download complete') - else: - logger.error('Failed to download JADX zip. ' - 'Status code: %s', response.status) - return + + downloaded_size = download_file(url, tmp_zip_file.name) + logger.info('JADX download complete. File size: %d bytes', downloaded_size) # Extract the zip file logger.info('Extracting JADX to %s', extract_dir) @@ -57,7 +92,6 @@ def install_jadx(mobsf_home, version='1.5.0'): logger.info('JADX installed successfully') except Exception: logger.exception('Error during JADX installation') - finally: if 'tmp_zip_file' in locals(): Path(tmp_zip_file.name).unlink() diff --git a/mobsf/MobSF/views/apk_downloader.py b/mobsf/MobSF/views/apk_downloader.py index 871cd2d1cb..ca5a0cd4c9 100644 --- a/mobsf/MobSF/views/apk_downloader.py +++ b/mobsf/MobSF/views/apk_downloader.py @@ -37,6 +37,7 @@ def fetch_html(url): try: proxies, verify = upstream_proxy('https') res = requests.get(url, + timeout=5, headers=headers, proxies=proxies, verify=verify, @@ -53,6 +54,7 @@ def download_file(url, outfile): logger.info('Downloading APK...') proxies, verify = upstream_proxy('https') with requests.get(url, + timeout=5, stream=True, proxies=proxies, verify=verify) as r: diff --git a/mobsf/StaticAnalyzer/views/android/manifest_analysis.py b/mobsf/StaticAnalyzer/views/android/manifest_analysis.py index 928ebb6f73..13100da50a 100755 --- a/mobsf/StaticAnalyzer/views/android/manifest_analysis.py +++ b/mobsf/StaticAnalyzer/views/android/manifest_analysis.py @@ -88,10 +88,10 @@ def _check_url(host, w_url): status_code = 0 r = requests.get(w_url, + timeout=5, allow_redirects=False, proxies=proxies, - verify=verify, - timeout=5) + verify=verify) status_code = r.status_code if status_code == 302: diff --git a/mobsf/StaticAnalyzer/views/android/playstore.py b/mobsf/StaticAnalyzer/views/android/playstore.py index 936e3f7d38..0ea296a02a 100644 --- a/mobsf/StaticAnalyzer/views/android/playstore.py +++ b/mobsf/StaticAnalyzer/views/android/playstore.py @@ -55,6 +55,7 @@ def app_search(checksum, app_id): try: proxies, verify = upstream_proxy('https') req = requests.get(req_url, + timeout=5, auth=(settings.APPMONSTA_API, 'X'), headers=headers, proxies=proxies, diff --git a/mobsf/StaticAnalyzer/views/common/firebase.py b/mobsf/StaticAnalyzer/views/common/firebase.py index 7004afc10d..1c1701a2d2 100644 --- a/mobsf/StaticAnalyzer/views/common/firebase.py +++ b/mobsf/StaticAnalyzer/views/common/firebase.py @@ -95,8 +95,11 @@ def open_firebase(checksum, url): ' AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/39.0.2171.95 Safari/537.36')} resp = requests.get( - base_url, headers=headers, - proxies=proxies, verify=verify, + base_url, + timeout=5, + headers=headers, + proxies=proxies, + verify=verify, allow_redirects=False) if resp.status_code == 200: return base_url, True @@ -176,7 +179,7 @@ def firebase_remote_config(checksum, code_an_dic): proxies, verify = upstream_proxy('https') response = requests.post( url, - timeout=4, + timeout=5, json=body, proxies=proxies, verify=verify, diff --git a/mobsf/StaticAnalyzer/views/ios/appstore.py b/mobsf/StaticAnalyzer/views/ios/appstore.py index b6da627ed2..d3c3c07623 100644 --- a/mobsf/StaticAnalyzer/views/ios/appstore.py +++ b/mobsf/StaticAnalyzer/views/ios/appstore.py @@ -29,8 +29,11 @@ def app_search(checksum, app_id): det = {} proxies, verify = upstream_proxy('https') req = requests.get( - req_url, headers=headers, - proxies=proxies, verify=verify) + req_url, + timeout=5, + headers=headers, + proxies=proxies, + verify=verify) resp = req.json() if resp['results']: det = resp['results'][0] diff --git a/poetry.lock b/poetry.lock index c88fdfd2b9..9080139eb5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -216,13 +216,13 @@ files = [ [[package]] name = "blinker" -version = "1.8.2" +version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, - {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, + {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, + {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, ] [[package]] @@ -2973,13 +2973,13 @@ bracex = ">=2.1.1" [[package]] name = "werkzeug" -version = "3.1.2" +version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" files = [ - {file = "werkzeug-3.1.2-py3-none-any.whl", hash = "sha256:4f7d1a5de312c810a8a2c6f0b47e9f6a7cffb7c8322def35e4d4d9841ff85597"}, - {file = "werkzeug-3.1.2.tar.gz", hash = "sha256:f471a4cd167233077e9d2a8190c3471c5bc520c636a9e3c1e9300c33bced03bc"}, + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index fe04f01657..1075cc32dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mobsf" -version = "4.1.6" +version = "4.1.7" description = "Mobile Security Framework (MobSF) is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis." keywords = ["mobsf", "mobile security framework", "mobile security", "security tool", "static analysis", "dynamic analysis", "malware analysis"] authors = ["Ajin Abraham "] diff --git a/scripts/update_android_permissions.py b/scripts/update_android_permissions.py index 7a283b4449..d0e4f77b10 100644 --- a/scripts/update_android_permissions.py +++ b/scripts/update_android_permissions.py @@ -11,7 +11,7 @@ ANDROID_PERMISSION_DOCS_URL = ('https://developer.android.com/' 'reference/android/Manifest.permission') -response = requests.get(ANDROID_PERMISSION_DOCS_URL) +response = requests.get(ANDROID_PERMISSION_DOCS_URL, timeout=5) content = Soup(response.content, 'html.parser') online_permissions = {}