├── Logs ├── last_seen_acunetix.log ├── Notes.txt ├── target.log ├── current_target.json └── heavy_scan_acunetix_target_log.json ├── requirements.txt ├── attachments ├── ram_1.png ├── note_1.png ├── add_target_1.png ├── add_target_2.png ├── add_target_3.png ├── auto_abort_1.png ├── auto_noti_1.png ├── set_target_1.png ├── start_scan_1.png ├── start_scan_2.png ├── stop_vuln_1.png ├── stop_vuln_2.png ├── vuln_type_1.png ├── get_api_token.png ├── vuln_detail_1.png ├── get_list_vulns_1.png ├── get_list_vulns_2.png ├── print_subdomain_1.png ├── stop_scan_acunetix_1.png ├── search_by_vuln_type_1.png └── get_processing_targets_1.png ├── .gitignore ├── Target_Logs └── vulnweb.com │ └── vulnweb.com_subdomains.txt ├── LICENSE ├── api.py ├── config.py ├── README.md ├── Acunetix.py ├── AcuScan.py └── Telegram_bot.py /Logs/last_seen_acunetix.log: -------------------------------------------------------------------------------- 1 | 2024-06-21T16:39:32.568958Z -------------------------------------------------------------------------------- /Logs/Notes.txt: -------------------------------------------------------------------------------- 1 | [2024-06-04 10:51:21] : [Note]: Check this note 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/requirements.txt -------------------------------------------------------------------------------- /Logs/target.log: -------------------------------------------------------------------------------- 1 | [2024-06-21 23:33:19] : [Acunetix Scan] - Start scan target http://www.vulnweb.com -------------------------------------------------------------------------------- /attachments/ram_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/ram_1.png -------------------------------------------------------------------------------- /attachments/note_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/note_1.png -------------------------------------------------------------------------------- /attachments/add_target_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/add_target_1.png -------------------------------------------------------------------------------- /attachments/add_target_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/add_target_2.png -------------------------------------------------------------------------------- /attachments/add_target_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/add_target_3.png -------------------------------------------------------------------------------- /attachments/auto_abort_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/auto_abort_1.png -------------------------------------------------------------------------------- /attachments/auto_noti_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/auto_noti_1.png -------------------------------------------------------------------------------- /attachments/set_target_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/set_target_1.png -------------------------------------------------------------------------------- /attachments/start_scan_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/start_scan_1.png -------------------------------------------------------------------------------- /attachments/start_scan_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/start_scan_2.png -------------------------------------------------------------------------------- /attachments/stop_vuln_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/stop_vuln_1.png -------------------------------------------------------------------------------- /attachments/stop_vuln_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/stop_vuln_2.png -------------------------------------------------------------------------------- /attachments/vuln_type_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/vuln_type_1.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | config.py 3 | 4 | # Test file 5 | test* 6 | resume.cfg 7 | test.json 8 | test2.py 9 | test.py -------------------------------------------------------------------------------- /attachments/get_api_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/get_api_token.png -------------------------------------------------------------------------------- /attachments/vuln_detail_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/vuln_detail_1.png -------------------------------------------------------------------------------- /attachments/get_list_vulns_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/get_list_vulns_1.png -------------------------------------------------------------------------------- /attachments/get_list_vulns_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/get_list_vulns_2.png -------------------------------------------------------------------------------- /attachments/print_subdomain_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/print_subdomain_1.png -------------------------------------------------------------------------------- /attachments/stop_scan_acunetix_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/stop_scan_acunetix_1.png -------------------------------------------------------------------------------- /attachments/search_by_vuln_type_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/search_by_vuln_type_1.png -------------------------------------------------------------------------------- /attachments/get_processing_targets_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTai261/Acunetix_tele_bot/HEAD/attachments/get_processing_targets_1.png -------------------------------------------------------------------------------- /Logs/current_target.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_target": { 3 | "example.com": { 4 | "is_scan_nuclei": false 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /Target_Logs/vulnweb.com/vulnweb.com_subdomains.txt: -------------------------------------------------------------------------------- 1 | http://www.vulnweb.com 2 | http://testhtml5.vulnweb.com 3 | http://testphp.vulnweb.com 4 | http://testasp.vulnweb.com 5 | http://rest.vulnweb.com 6 | http://testaspnet.vulnweb.com 7 | -------------------------------------------------------------------------------- /Logs/heavy_scan_acunetix_target_log.json: -------------------------------------------------------------------------------- 1 | { 2 | "heavy_targets": { 3 | "451f3b91-4917-4dff-84d3-dbc3f4c9bc99": { 4 | "address": "https://example.com", 5 | "target_id": "58647a18-536b-45df-8499-1a898c3904fb", 6 | "is_remind": false, 7 | "is_auto_stop": true 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 DTai261 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 | -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | import jwt 3 | import requests 4 | from config import SECRET_KEY, HOST, ACUNETIX_API_KEY 5 | import Acunetix 6 | 7 | headers = {"X-Auth":ACUNETIX_API_KEY,"content-type": "application/json"} 8 | 9 | app = Flask(__name__) 10 | 11 | def verify_token(token): 12 | try: 13 | payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) 14 | return payload['user_id'] 15 | except jwt.ExpiredSignatureError: 16 | return None 17 | except jwt.InvalidTokenError: 18 | return None 19 | 20 | @app.route('/api/start', methods=['POST']) 21 | def start(): 22 | token = request.headers.get('Authorization') 23 | if not token: 24 | return jsonify({"response": "Missing token."}), 401 25 | 26 | user_id = verify_token(token) 27 | if not user_id: 28 | return jsonify({"response": "Invalid or expired token."}), 401 29 | 30 | return jsonify({"response": "Hello! This is a response from the /api/start endpoint."}) 31 | 32 | @app.route('/api/acunetix/', methods=['GET', 'POST']) 33 | def acunetix(forward_part): 34 | token = request.headers.get('Authorization') 35 | if not token: 36 | return jsonify({"response": "Missing token."}), 401 37 | 38 | user_id = verify_token(token) 39 | if not user_id: 40 | return jsonify({"response": "Invalid or expired token."}), 401 41 | 42 | url = f"{HOST}/api/v1/{forward_part}" 43 | 44 | if request.method == 'POST': 45 | response = requests.post(url, headers=headers, json=request.json, verify=False) 46 | else: # GET request 47 | response = requests.get(url, headers=headers, params=request.args, verify=False) 48 | 49 | return jsonify(response.json()), response.status_code 50 | 51 | 52 | if __name__ == '__main__': 53 | app.run(host='0.0.0.0', port=5000) 54 | 55 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | ACUNETIX_API_KEY="xxxxxxxxxxxxxxxxxxx" 2 | HOST= "https://localhost:13443" 3 | #Number of target scan at the same time 4 | NUMBER_OF_TARGETS_SIMULTANEOUSLY = 3 5 | API_SERVER = True 6 | 7 | #scan profile here: 8 | SCAN_PROFILE = { 9 | "name": "AcuScan Auto Profile", 10 | "custom": "true", 11 | "checks": [ 12 | "wvs/RPA/InsecureTransition.js", 13 | "wvs/httpdata/mixed_content_over_https.js", 14 | "wvs/RPA/Cookie_On_Parent_Domain.js", 15 | "wvs/RPA/Cookie_Without_HttpOnly.js", 16 | "wvs/RPA/Cookie_Without_Secure.js", 17 | "wvs/RPA/Cookie_Validator.js", 18 | "wvs/RPA/SRI_Not_Implemented.js", 19 | "wvs/Scripts/PerServer/Reverse_Proxy_Bypass.script", 20 | "wvs/target/RevProxy_Detection.js", 21 | "wvs/Crawler/HTTPS_insecure_maxTLS.js", 22 | "wvs/Scripts/PerFile/Javascript_Libraries_Audit.script", 23 | "wvs/httpdata/javascript_library_audit_external.js", 24 | "wvs/httpdata/permissions_policy.js", 25 | "wvs/httpdata/CSP_not_implemented.js", 26 | "wvs/httpdata/content_security_policy.js", 27 | "wvs/httpdata/rails_accept_file_content_disclosure.js", 28 | "wvs/httpdata/X_Frame_Options_not_implemented.js", 29 | "wvs/httpdata/iframe_sandbox.js", 30 | "wvs/Scripts/PerServer/SSL_Audit.script", 31 | "wvs/target/ssltest", 32 | "wvs/httpdata/HSTS_not_implemented.js", 33 | "ovas/", 34 | ], 35 | } 36 | 37 | SCAN_PROFILE_ID = 'xxxxxxxxxxxxxxxxxxxxxxxxx' 38 | OUT_SCOPE_VULN_ACUNETIX = [ 39 | '26f9af3b-acf6-f3e4-0fdc-567ac9e03527', 40 | 'bca221d1-8581-3375-4097-66b0048ed088', 41 | '3f6a8a0e-07f2-af81-54ff-61020299caeb', 42 | '34a6c791-c497-27d5-7272-6a968e9fdccb', # HTTP Strict Transport Security (HSTS) Policy Not Enabled 43 | '391f39d7-6805-cd0c-44ec-df4bf71273eb', # Host header attack 44 | '84fd0f24-a88f-09cf-97eb-67959deb26d4', # Vulnerable JavaScript libraries 45 | 'dc80dd1d-735b-4ba7-b279-589743eeba6e', # WordPress Deserialization of Untrusted Data Vulnerability 46 | '029afcbb-3ec2-be3c-5cc3-29f5cfe016f4', # SSL Certificate Is About To Expire 47 | '253317f4-6382-e8f5-acd2-b69f488adc11', # WordPress 6.0.x Multiple Vulnerabilities 48 | '59ec3cbe-d67f-6a74-6aa1-f449d224ab71' # Vulnerable package dependencies 49 | ] 50 | # Time to scan acunetix (hours) after this time the tool will auto stop the target unless user config not auto abort on that target 51 | SCAN_TIME = 24 52 | 53 | NOTIFICATION = False 54 | 55 | #telegram bot api key: 56 | TELEGRAM_API_KEY = 'xxxxxxxxxxxxxxxx' 57 | 58 | #id of user that allowed to send command 59 | ALLOWED_USER_ID = 000000000 60 | 61 | OUT_SCOPE_VULN_NUCLEI = ['CVE-xxxx-xxxx'] 62 | 63 | API_PORT = 5000 64 | # If TOKEN_EXPIRATION is None, the token will not expire 65 | TOKEN_EXPIRATION = 3600 # 1 hour 66 | 67 | #SECRET_KEY for the JWT 68 | SECRET_KEY = 'YOUR_SECRET_KEY' # Use a secure, random secret key for JWT 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Acunetix telegram bot 3 |
4 |

5 | 6 |

Telegram bot designed to manage and automate your Acunetix vulnerability scans

7 | 8 |

9 | 📖 About • 10 | 📋 Features • 11 | 🏗️ Installation • 12 | ⛏️ Usage • 13 | 🚀 Examples 14 |

15 | 16 | 17 | ## About 18 | The `Acunetix telegram bot` is a powerful and convenient Telegram bot designed to manage and automate your Acunetix vulnerability scans. This bot allows you to control your Acunetix scans directly from your Telegram account through simple commands. 19 | 20 | 21 | ## Features 22 | - Features 23 | - Automated Target Addition: Easily add new targets to your Acunetix scan queue via Telegram commands. 24 | - Real-Time Vulnerability Alerts: Receive instant notifications on Telegram whenever new vulnerabilities are discovered during scans. 25 | - Detailed Vulnerability Information: Get comprehensive details about each detected vulnerability directly in your Telegram chat, enabling quick assessment and action. 26 | - Convenient Scan Management: Start, stop, and manage your scans with simple and intuitive Telegram commands. 27 | - User Restriction: Only users specified in the configuration file can issue commands to the bot, ensuring controlled access. Currently, the bot sends all updates and vulnerability details to a single authorized user, even if the bot is added to a group. 28 | - API interaction: By leveraging Acunetix's own API, this feature enables users to perform various operations without directly exposing the Acunetix server 29 | - Future Enhancements 30 | - Group Command: Plans to add support for group commands, allowing the bot to accept commands from multiple authorized users within a group and send updates to the entire group. 31 | 32 | ## Installation 33 | **Prerequire**: 34 | You must be install the following tools first: [subfinder](https://github.com/projectdiscovery/subfinder), [httpx](https://github.com/projectdiscovery/httpx)(1), [nuclei](https://github.com/projectdiscovery/nuclei)(2), [notify](https://github.com/projectdiscovery/notify) 35 | 36 | To install `Acunetix telegram bot`, follow these steps: 37 | ```sh 38 | git clone https://github.com/DTai261/Acunetix_tele_bot 39 | cd Acunetix_tele_bot 40 | pip install -r requirements.txt 41 | 42 | # Edit the file config.py then run the bot: 43 | python Telegram_bot.py 44 | ``` 45 | 46 | ## Usage 47 | 48 | The following command are use for control the acunetix scan using telegram. More details in the examples.
49 | `/start`: just a start !
50 | `/note`: write a note
51 | `/read_note`: retrive note, can add arg as number of line
52 | `/help`: help !
53 | `/add_target`: add wildcard target to find it subdomains.
54 | `/start_scan`: start scan on wildcard / single target
55 | `/set_targets`: Set number of targets simultaneously scan acunetix
56 | `/list_target`: list all the target add by `/add_target` that not been scan nuclei
57 | `/print_subdomains`: print subdomains of target
58 | `/stop_vuln`: add vuln to list out of scope
59 | `/auto_abort_scan_true`: set auto abort scan after x hours for all targets (default)
60 | `/auto_abort_scan_false`: unset auto abort scan after x hours for all targets
61 | `/ram`: show system memory usage
62 | `/get_processing_targets`: list all the target have status proccessing acunetix
63 | `/stop_scan_acunetix`: stop an acunetix scan by scan_id
64 | `/get_list_vulns`: get all the vuln by filter eg: `/get_list_vulns 2,3,4 https://example.com, https://google.com`
65 | `/vuln_detail`: get vuln detail by vuln id
66 | `/vuln_type`: get all scanned vulns by severity (1-4)
67 | `/search_vuln`: search for vuln has been scaned
68 | `/manual_activate_auto_scan`: Manual activate auto scan after it dead. Not sure if this work :)
69 | `/notification`: Enable or disable new vulnerabilities notifications from the bot
70 | `/get_api_token`: generate a new API token that can be used for authenticating requests to the associated API server
71 | 72 | ## Examples 73 | 74 | ### `/add_target` 75 | When user provide domain target, the bot will scan subdomain using subfinder and httpx then add them to the Acunetix target list. 76 | 77 | 78 | 85 | 91 | 92 |
79 | 1 80 |
81 |
82 | /add_target <domain> 83 |
84 |
86 | 2 87 |
88 |
89 | subdomain will be save to Target_Logs/<domain>/<domain>_subdomain.txt 90 |
93 | 3 94 |
95 | 96 | ### `/print_subdomains` 97 | 98 | 99 | 105 | 106 |
100 | 4 101 |
102 | /print_subdomains <domain> 103 |
104 |
107 | 108 | ### `/start_scan` 109 | Add single target to Acunetix scan list and scan it immediately. 110 | 111 | 112 | 118 | 122 | 123 |
113 | 5 114 |
115 | /start_scan <target URL> 116 |
117 |
119 | 6 120 |
121 |
124 | 125 | ### `/get_processing_targets` 126 | 127 | 128 | 134 | 135 |
129 | 7 130 |
131 | /get_processing_targets: get processing targets 132 |
133 |
136 | 137 | ### `/set_targets` 138 | Set number of concurrent targets. If the current number of targets is larger than the set number, the bot will do nothing but wait for one of the targets end, or force to end by the scan time in config file. 139 | 140 | 141 | 147 | 148 |
142 | 8 143 |
144 | /set_target <int> 145 |
146 |
149 | 150 | ### `/stop_scan_acunetix` 151 | Stop scan by it id. You can get the scan id of the target by using command /get_processing_targets. 152 | 153 | 154 | 160 | 161 |
155 | 8 156 |
157 | /set_target: <scan_id> 158 |
159 |
162 | 163 | ### `Auto notification when new vuln detected` 164 | The bot will auto check for new vulns found by Acunetix of all targets every 1 min, if there are new vuln have severity from medium-critical it will sent user the vuln detail. 165 |
9 166 | 167 | ### `/stop_vuln` 168 | To stop receive a specific vuln you can use command /stop_vuln vuln_id. You can get the vuln id at the end of the vuln detail message or use command /vuln_type int or command /get_list_vulns int 169 | 170 | 171 | 177 | 178 |
172 | 10 173 |
174 | /stop_vuln <vuln_id> 175 |
176 |
179 | 180 | ### `/vuln_type` 181 | Find vuln type by severity (1-4: 4 is critical, 1 is info) on all targets. Eg: search for high severity vulns: 182 | 183 | 184 | 190 | 191 |
185 | 11 186 |
187 | /vuln_type <int(1-4)> 188 |
189 |
192 | 193 | ### `/search_by_vuln_type` 194 | Search all the targets that have specific vulnerabilities by vuln type id. 195 | 196 | 197 | 203 | 204 |
198 | 12 199 |
200 | /search_by_vuln_type <vuln_type_id> 201 |
202 |
205 | 206 | ### `/get_list_vulns` 207 | Or you can search for specific severity for specific target URL. 208 | 209 | 210 | 216 | 221 | 222 |
211 | 13 212 |
213 | /get_list_vulns <severity> <target URL> 214 |
215 |
217 | 14 218 |
219 |
220 |
223 | 224 | ### `/vuln_detail` 225 | Get the vuln detail by id: http request, highlighted, references, ... 226 | 227 | 228 | 234 | 235 |
229 | 15 230 |
231 | /vuln_detail <vuln_id> 232 |
233 |
236 | 237 | ### `Another command:` 238 | 239 | 240 | 246 | 252 | 253 | 254 | 259 | 264 | 265 |
241 | 16 242 |
243 | /ram 244 |
245 |
247 | 17 248 |
249 | /note / /read_note 250 |
251 |
255 | 18 256 |
257 | /auto_abort_scan_false / /auto_abort_scan_true 258 |
260 | 19 261 |
262 | /get_api_token 263 |
266 | 267 | ### Note 268 | ###### 1: 269 | - It may have a conflict between httpx module of python and the httpx tool so you can modify this [line](https://github.com/DTai261/Acunetix_tele_bot/blob/main/AcuScan.py#L158) to the absolute path of the binary file. 270 | 271 | ###### 2: 272 | - Currently scan nuclei not available yet because I did some stupid stuff with it :v I may update it in the future. 273 | -------------------------------------------------------------------------------- /Acunetix.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import config 3 | import sys 4 | import json 5 | import urllib3 6 | import os 7 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 8 | from datetime import datetime 9 | 10 | sys.dont_write_bytecode = True 11 | 12 | heavy_scan_acunetix_target_log = "Logs/heavy_scan_acunetix_target_log.json" 13 | apikey = config.ACUNETIX_API_KEY 14 | scan_profile = config.SCAN_PROFILE 15 | number_of_target_simultaneously = config.NUMBER_OF_TARGETS_SIMULTANEOUSLY 16 | is_check_simultaneously = True 17 | host = config.HOST 18 | proxy_url = 'http://127.0.0.1:8080' 19 | proxies = { 20 | 'http': proxy_url, 21 | 'https': proxy_url 22 | } 23 | 24 | headers = {"X-Auth":apikey,"content-type": "application/json"} 25 | 26 | def AddTarget(ListTarget: list, group: list =None) -> list: 27 | if group == "": 28 | groups = [] 29 | else: 30 | groups = [group] if group else [] 31 | data = { 32 | "targets": ListTarget, 33 | "groups": groups 34 | } 35 | response = requests.post(host+"/api/v1/targets/add", data=json.dumps(data), headers=headers, verify=False) 36 | json_response = json.loads(response.text) 37 | targets = json_response.get('targets') 38 | # Return list of target 39 | return targets 40 | 41 | def AddGroup(GroupName: str) -> str: 42 | count = 0 43 | while True: 44 | if count == 0: 45 | json_data = { 46 | 'name': f'{GroupName}', 47 | 'description': '', 48 | } 49 | else: 50 | json_data = { 51 | 'name': f'{GroupName} ({count})', 52 | 'description': '', 53 | } 54 | response = requests.post(f'{host}/api/v1/target_groups', headers=headers, json=json_data, verify=False) 55 | if (response.status_code != 409): 56 | json_response = json.loads(response.text) 57 | group_id = json_response.get('group_id') 58 | return group_id 59 | else: 60 | count += 1 61 | 62 | if count > 10: 63 | print('[ERROR] - Some thing wrong in Acunetix.AddGroup function') 64 | return None 65 | 66 | #Get target list by "never scan", target group, address. Output is the list of target. eg: [{address_1,target_id_1},{address_2,target_id_2}] 67 | def GetListTarget(never_scan: str ='never_scanned', target_address: str =None, target_group: str =None) -> list: 68 | is_never_scan = "" 69 | text_search = "text_search:*" 70 | group_id = "group_id:" 71 | 72 | if(never_scan != None): 73 | is_never_scan = never_scan 74 | if(target_group != None): 75 | group_id = f'group_id:{target_group}' 76 | if(target_address != None): 77 | text_search = f'text_search:*{target_address}' 78 | 79 | query = f'{is_never_scan};{text_search};{group_id};' 80 | response = requests.get(f'{host}/api/v1/targets?l=20&q={query}', headers=headers, verify=False) 81 | json_response = json.loads(response.text) 82 | 83 | result_list = [] 84 | 85 | for target in json_response.get("targets", []): 86 | address = target.get("address") 87 | target_id = target.get("target_id") 88 | 89 | if address and target_id: 90 | result_list.append({"address": address, "target_id": target_id}) 91 | 92 | return result_list 93 | 94 | # Return the number of targets left 95 | def GetTargetsLeft() -> int: 96 | response = requests.get(f'{host}/api/v1/targets?l=1&q=never_scanned;', headers=headers, verify=False) 97 | json_response = json.loads(response.text) 98 | count = json_response.get('pagination').get('count') 99 | return count 100 | 101 | 102 | def StartScan(target_id: str, profile_id: str) -> None: 103 | json_data = { 104 | 'profile_id': f'{profile_id}', 105 | 'incremental': 'false', 106 | 'schedule': { 107 | 'disable': 'false', 108 | 'start_date': None, 109 | 'time_sensitive': 'false', 110 | }, 111 | 'target_id': f'{target_id}', 112 | } 113 | 114 | response = requests.post(f'{host}/api/v1/scans', headers=headers, json=json_data, verify=False) 115 | if response.status_code == 409: 116 | print(f'[ERROR] - Acunetix.StartScan\n{json.loads(response.text)}') 117 | 118 | def AddScanProfile() -> str: 119 | response = requests.post(f'{host}/api/v1/scanning_profiles', headers=headers, json=scan_profile, verify=False) 120 | json_response = json.loads(response.text) 121 | profile_id = json_response.get('profile_id') 122 | return profile_id 123 | 124 | def SortBySeverity(data: list) -> list: 125 | return sorted(data, key=lambda x: x.get("severity", 0),reverse=True) 126 | 127 | 128 | def GetVulnType() -> list: 129 | response = requests.get(f'{host}/api/v1/vulnerability_types?q=text_search:*', headers=headers, verify=False) 130 | json_response = json.loads(response.text).get('vulnerability_types') 131 | list_vuln_type = [{"name": item["name"], "vt_id": item["vt_id"], "severity": item["severity"]} for item in json_response] 132 | list_vuln_type = SortBySeverity(list_vuln_type) 133 | return list_vuln_type 134 | 135 | 136 | def GetLatestVuln(LastSeen: str =None, severity: str ='2,3,4', vt_id: str ='') -> list: 137 | CursorsId = '' 138 | #format the lastseen value 139 | Formated_LastSeen = Format_DateTime(LastSeen) 140 | 141 | 142 | List_vulns = [] 143 | is_latest = False 144 | while True: 145 | response = requests.get(f'{host}/api/v1/vulnerabilities?c={CursorsId}&l=100&q=vt_id:{vt_id};severity:{severity};date:>{Formated_LastSeen};status:!ignored;status:!fixed;&s=last_seen:desc', headers=headers, verify=False) 146 | json_data = json.loads(response.text) 147 | List_vuln = json_data.get('vulnerabilities') 148 | if is_latest == False: 149 | # print(List_vuln[0].get('last_seen')) 150 | try: 151 | lastest = Format_DateTime(List_vuln[0].get('last_seen')) 152 | UpdateLogFile(lastest) 153 | except IndexError: 154 | pass 155 | is_latest= True 156 | 157 | List_vuln_filtered = [ 158 | { 159 | "affects_url": item["affects_url"], 160 | "severity" : item["severity"], 161 | "confidence": item["confidence"], 162 | "target_id": item["target_id"], 163 | "vt_id": item["vt_id"], 164 | "vt_name": item["vt_name"], 165 | "vuln_id": item["vuln_id"] 166 | 167 | } 168 | for item in List_vuln 169 | ] 170 | 171 | List_vulns.extend(List_vuln_filtered) 172 | 173 | Cursors_list = json_data.get('pagination').get('cursors') 174 | if len(Cursors_list) > 1: 175 | CursorsId = Cursors_list[1] 176 | if len(Cursors_list) == 1: 177 | break 178 | 179 | return List_vulns 180 | 181 | 182 | def Format_DateTime(LastSeen: str) -> str: 183 | Formated_LastSeen = '' 184 | if LastSeen != None: 185 | plus_index = LastSeen.find('+') 186 | if plus_index != -1: 187 | Formated_LastSeen = LastSeen[:plus_index] + 'Z' 188 | else: 189 | Formated_LastSeen = LastSeen 190 | return Formated_LastSeen 191 | 192 | def UpdateLogFile(new_datetime: str) -> None: 193 | log_file_path = "Logs/last_seen_acunetix.log" 194 | 195 | try: 196 | # Read the current datetime from the log file 197 | with open(log_file_path, 'r') as file: 198 | current_datetime_str = file.read().strip() 199 | 200 | if not current_datetime_str: 201 | # If the file is empty, write the new datetime 202 | with open(log_file_path, 'w') as file: 203 | file.write(new_datetime) 204 | else: 205 | # Parse datetimes 206 | current_datetime = datetime.strptime(current_datetime_str, "%Y-%m-%dT%H:%M:%S.%fZ") 207 | new_datetime_obj = datetime.strptime(new_datetime, "%Y-%m-%dT%H:%M:%S.%fZ") 208 | 209 | # Check if the new datetime is later than the current datetime 210 | if new_datetime_obj > current_datetime: 211 | # Overwrite the log file with the new datetime 212 | with open(log_file_path, 'w') as file: 213 | file.write(new_datetime) 214 | else: 215 | pass 216 | 217 | except FileNotFoundError: 218 | # If the log file doesn't exist, create it with the given datetime 219 | with open(log_file_path, 'w') as file: 220 | file.write(new_datetime) 221 | 222 | #status: completed, processing, ... 223 | def GetInProcessTargets(status: str) -> dict: 224 | response = requests.get(f'{host}/api/v1/scans?l=20&q=status:{status};', headers=headers,verify=False) 225 | json_response = json.loads(response.text) 226 | return json_response 227 | 228 | def update_scan_profile_id(start_with: str, new_id:str) -> None: 229 | config.SCAN_PROFILE_ID = new_id 230 | # Read the contents of the file 231 | file_path = 'config.py' 232 | with open(file_path, 'r') as file: 233 | lines = file.readlines() 234 | 235 | 236 | for i, line in enumerate(lines): 237 | if line.startswith(f'{start_with}'): 238 | lines[i] = f"{start_with} = '{new_id}'\n" 239 | break 240 | 241 | # Write the updated contents back to the file 242 | with open(file_path, 'w') as file: 243 | file.writelines(lines) 244 | 245 | def CheckScanProfile() -> str: 246 | profile = False 247 | response = requests.get(f'{host}/api/v1/scanning_profiles', headers=headers, verify=False) 248 | if response.status_code != 409: 249 | scanning_profiles = json.loads(response.text).get('scanning_profiles') 250 | for scan_profile in scanning_profiles: 251 | if scan_profile.get('name') == 'AcuScan Auto Profile': 252 | profile = True 253 | else: 254 | print(f'[ERROR] - CheckScanProfile function') 255 | profile_id = None 256 | if not profile: 257 | profile_id = AddScanProfile() 258 | update_scan_profile_id('SCAN_PROFILE_ID', profile_id) 259 | print('AcuScan Auto Profile is loaded') 260 | return profile_id 261 | 262 | 263 | def get_vuln_details(vuln_id: str) -> dict: 264 | response = requests.get(f'{host}/api/v1/vulnerabilities/{vuln_id}',headers=headers,verify=False) 265 | json_response = json.loads(response.text) 266 | highlights = json_response.get('highlights') 267 | details = json_response.get('details') 268 | request = json_response.get('request') 269 | 270 | response = requests.get(f'{host}/api/v1/vulnerabilities/{vuln_id}/http_response',headers=headers,verify=False) 271 | http_response = response.text 272 | 273 | highlighted_text = "" 274 | for highlight in highlights: 275 | index = highlight.get("index", 0) 276 | length = highlight.get("length", 0) 277 | if(length > 100): 278 | break 279 | if len(http_response) <= 100: 280 | index -= 10 281 | length += 10 282 | if len(http_response) > 100 : 283 | index -= 20 284 | length += 20 285 | highlighted_text += f'{http_response[index:index+length]}\n' 286 | 287 | if highlighted_text == "": 288 | highlighted_text = f"The highlighted is too long for the message to handle\n" 289 | 290 | Vuln_detail ={ 291 | "highlighted_text" : highlighted_text, 292 | "details" : details, 293 | "request" : request 294 | } 295 | return Vuln_detail 296 | 297 | 298 | def GetTargetScanInfo(target_address: str=None, target_id: str=None) -> dict: 299 | if target_id == None: 300 | response = requests.get(f'{host}/api/v1/targets?l=100&q=text_search:*{target_address};', headers=headers, verify=False) 301 | target_info = json.loads(response.text).get('targets')[0] 302 | target_id = target_info.get('target_id') 303 | response = requests.get(f'{host}/api/v1/scans?l=20&q=status:processing;target_id:{target_id};', headers=headers, verify=False) 304 | json_response = json.loads(response.text) 305 | return json_response 306 | 307 | 308 | def StopScan(target_address: str=None, scan_id: str=None) -> None: 309 | if scan_id == None: 310 | response = requests.get(f'{host}/api/v1/targets?l=100&q=text_search:*{target_address};', headers=headers, verify=False) 311 | target_info = json.loads(response.text).get('targets')[0] 312 | scan_id = target_info.get('target_id') 313 | json_data = {} 314 | requests.post(f'{host}/api/v1/scans/{scan_id}/abort', headers=headers, json=json_data, verify=False) 315 | 316 | # StopScan(target_id='18361c3d-2a75-41a7-9883-e9345dd31716') 317 | # print(GetTargetScanInfo(target_id='18361c3d-2a75-41a7-9883-e9345dd31716')) 318 | 319 | def GetDetailFromScanId(scan_id: str) -> dict: 320 | response = requests.get(f'{host}/api/v1/scans/{scan_id}', headers=headers, verify=False) 321 | json_response = json.loads(response.text) 322 | return json_response 323 | 324 | def add_to_heavy_scan_acunetix_target_log(scan_id: str=None, target_id: str = None, target_address: str=None, is_remind: str=True, is_auto_stop: str=True) -> None: 325 | if scan_id == None: 326 | response = requests.get(f'{host}/api/v1/targets?l=100&q=text_search:*{target_address};', headers=headers, verify=False) 327 | target_info = json.loads(response.text).get('targets')[0] 328 | scan_id = target_info.get('target_id') 329 | # Check if the file exists 330 | if not os.path.exists(heavy_scan_acunetix_target_log): 331 | # Create a new file with initial data if it does not exist 332 | data = { 333 | 'heavy_targets' : { 334 | f'{scan_id}': { 335 | 'address' : target_address, 336 | 'target_id' : target_id, 337 | 'is_remind' : is_remind, 338 | 'is_auto_stop': is_auto_stop 339 | } 340 | } 341 | } 342 | with open(heavy_scan_acunetix_target_log, 'w') as file: 343 | json.dump(data, file, indent=4) 344 | else: 345 | # Read the existing data from the file 346 | with open(heavy_scan_acunetix_target_log, 'r') as file: 347 | data = json.load(file) 348 | 349 | if scan_id not in data['heavy_targets']: 350 | data['heavy_targets'][scan_id] = { 351 | 'address': target_address, 352 | 'target_id' : target_id, 353 | 'is_remind': is_remind, 354 | 'is_auto_stop': is_auto_stop 355 | } 356 | else: 357 | # Add the new target with the specified is_scan_nuclei value 358 | data['heavy_targets'][scan_id]['is_remind'] = is_remind 359 | data['heavy_targets'][scan_id]['is_auto_stop'] = is_auto_stop 360 | 361 | # Write the updated data back to the file 362 | with open(heavy_scan_acunetix_target_log, 'w') as file: 363 | json.dump(data, file, indent=4) 364 | 365 | 366 | def delete_to_heavy_scan_acunetix_target_log(target_to_delete: str) -> None: 367 | # Read the data from the file 368 | with open(heavy_scan_acunetix_target_log, 'r') as file: 369 | data = json.load(file) 370 | 371 | # Delete the target if it exists 372 | if target_to_delete in data['heavy_targets']: 373 | del data['heavy_targets'][target_to_delete] 374 | 375 | # Write the updated data back to the file 376 | with open(heavy_scan_acunetix_target_log, 'w') as file: 377 | json.dump(data, file, indent=4) 378 | -------------------------------------------------------------------------------- /AcuScan.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import config 4 | import json 5 | import Acunetix 6 | from datetime import datetime 7 | # from Telegram_bot import send_message 8 | # import Telegram_bot 9 | import telegram.ext 10 | from telegram import ParseMode 11 | import threading 12 | import time 13 | import html 14 | import re 15 | 16 | target_log = "Logs/target.log" 17 | current_target_log = "Logs/current_target.json" 18 | heavy_scan_acunetix_target_log = "Logs/heavy_scan_acunetix_target_log.json" 19 | out_scope_vuln_nuclei = config.OUT_SCOPE_VULN_NUCLEI 20 | out_scope_vuln_acunetix = config.OUT_SCOPE_VULN_ACUNETIX 21 | 22 | api_key = config.TELEGRAM_API_KEY 23 | chat_id = config.ALLOWED_USER_ID 24 | profile_id = config.SCAN_PROFILE_ID 25 | config.NUMBER_OF_TARGETS_SIMULTANEOUSLY 26 | is_check_simultaneously = True 27 | is_scan_dead = False 28 | 29 | updater = telegram.ext.Updater(api_key, use_context=True) 30 | disp = updater.dispatcher 31 | 32 | def send_message(msg: str, updater=updater) -> None: 33 | try: 34 | updater.bot.send_message(chat_id=chat_id, text=msg, disable_web_page_preview=True) 35 | except Exception as e: 36 | print(f'[ERROR] - send_message function\n{e}') 37 | time.sleep(1) 38 | updater = telegram.ext.Updater(api_key, use_context=True) 39 | updater.bot.send_message(chat_id=chat_id, text=msg, disable_web_page_preview=True) 40 | 41 | # Send markdown message to user: *Bold* , _italic_ , `click to copy` , [link](https://example.com) 42 | def send_message_markdown(msg: str, updater=updater) -> None: 43 | try: 44 | updater.bot.send_message(chat_id=chat_id, text=msg, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 45 | except Exception as e: 46 | print(f'[ERROR] - send_message_markdown function\n{e}') 47 | time.sleep(1) 48 | updater = telegram.ext.Updater(api_key, use_context=True) 49 | updater.bot.send_message(chat_id=chat_id, text=msg, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 50 | 51 | def send_html_message(html_content: str) -> None: 52 | modified_html_content = html_content.replace('', '\n') 54 | modified_html_content = re.sub(r'<\s*p[^>]*>', '', modified_html_content) 55 | modified_html_content = re.sub(r'<\s*/\s*p\s*>', '', modified_html_content) 56 | try: 57 | updater.bot.send_message(chat_id=chat_id, text=modified_html_content, parse_mode=ParseMode.HTML, disable_web_page_preview=True) 58 | except Exception as e: 59 | print(f'[ERROR] - send_html_message\n{e}') 60 | 61 | 62 | def write_logs(message: str, log_path: str) -> None: 63 | current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 64 | if not os.path.exists(log_path): 65 | # Create the log file if it doesn't exist 66 | with open(log_path, "w") as f: 67 | f.write("") 68 | with open(log_path, "r") as f: 69 | content = f.read() 70 | new_content = f"[{current_datetime}] : {message}\n{content}" 71 | with open(log_path, "w") as f: 72 | f.write(new_content) 73 | pass 74 | 75 | 76 | def get_targets_not_scan_nuclei() -> list: 77 | false_targets = [] 78 | # Read the data from the file 79 | with open(current_target_log, 'r') as file: 80 | data = json.load(file) 81 | 82 | # Collect targets with "is_scan_nuclei" set to false 83 | for target, properties in data['current_target'].items(): 84 | if not properties['is_scan_nuclei']: 85 | false_targets.append(target) 86 | 87 | return false_targets 88 | 89 | 90 | def add_to_current_target_log(new_target: str, is_scan_nuclei: bool) -> None: 91 | # Check if the file exists 92 | if not os.path.exists(current_target_log): 93 | # Create a new file with initial data if it does not exist 94 | data = { 95 | "current_target": { 96 | new_target: {"is_scan_nuclei": is_scan_nuclei} 97 | } 98 | } 99 | with open(current_target_log, 'w') as file: 100 | json.dump(data, file, indent=4) 101 | else: 102 | # Read the existing data from the file 103 | with open(current_target_log, 'r') as file: 104 | data = json.load(file) 105 | 106 | # Add the new target with the specified is_scan_nuclei value 107 | data['current_target'][new_target] = {"is_scan_nuclei": is_scan_nuclei} 108 | 109 | # Write the updated data back to the file 110 | with open(current_target_log, 'w') as file: 111 | json.dump(data, file, indent=4) 112 | 113 | 114 | def delete_from_current_target_log(target_to_delete: str) -> None: 115 | # Read the data from the file 116 | with open(current_target_log, 'r') as file: 117 | data = json.load(file) 118 | 119 | # Delete the target if it exists 120 | if target_to_delete in data['current_target']: 121 | del data['current_target'][target_to_delete] 122 | 123 | # Write the updated data back to the file 124 | with open(current_target_log, 'w') as file: 125 | json.dump(data, file, indent=4) 126 | 127 | 128 | def is_out_scope_vuln(string: str) -> bool: 129 | for item in out_scope_vuln_nuclei: 130 | if item in string: 131 | return True 132 | return False 133 | 134 | def check_file(file_path: str) -> bool: 135 | # Check if the file exists 136 | if os.path.exists(file_path): 137 | # Check if the file is empty 138 | if os.path.getsize(file_path) < 3: 139 | return False 140 | else: 141 | return True 142 | else: 143 | return False 144 | 145 | def run_subfinder_httpx(target: str) -> None: 146 | try: 147 | target_count = 0 148 | print(f'Start finding subdomain for {target}... ') 149 | send_message(f'Start finding subdomain for {target}... ') 150 | # Create the directory for the target if it doesn't exist 151 | target_directory = os.path.join("Target_Logs", target) 152 | os.makedirs(target_directory, exist_ok=True) 153 | output_file_path = os.path.join(target_directory, f"{target}_subdomains.txt") 154 | 155 | if not check_file(output_file_path): 156 | pass 157 | # Run the command and capture its output 158 | command = f"subfinder -d {target} --silent | httpx --silent" 159 | try: 160 | # output = subprocess.check_output(command, shell=True, text=True) 161 | process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 162 | with open(output_file_path, "w") as f: 163 | for line in process.stdout: 164 | f.write(line) # Write each line to the file 165 | target_count += 1 166 | except subprocess.CalledProcessError as e: 167 | print(f"Error: {str(e)[:33]}") 168 | log_msg = f'[__ERROR__] - {target} - {str(e)[:33]}' 169 | return 170 | 171 | # Write to log file 172 | log_msg = f'[Scan Subdomain] - {target}' 173 | print(f'Done find subdomains for {target}.') 174 | write_logs(log_msg, target_log) 175 | else: 176 | with open(output_file_path, 'r') as file: 177 | target_count = len(file.readlines()) 178 | add_to_current_target_log(target, False) 179 | 180 | group_id = Acunetix.AddGroup(f'{target} subdomains') 181 | Add_Acunetix_targets(target, f'Auto Scan tool for {target}', group_id) 182 | # config_file_path = os.path.join(target_directory, f"Is_Scan_Nuclei.0") 183 | if target_count == 0: 184 | send_message(f'there is no subdomain for this target: {target}') 185 | if target_count == 1: 186 | send_message(f'there is 1 subdomain for this target: {target}') 187 | if target_count > 1: 188 | send_message(f'there are {target_count} subdomains for this target: {target}') 189 | send_message_markdown(f'Use command `/start_scan {target}` to start scan Acunetix on target') 190 | except Exception as e: 191 | print(f'[ERROR] - run_subfinder_httpx\n {str(e)[:33]}') 192 | write_logs(f'[ERROR] - run_subfinder_httpx: {str(e)[:33]}', target_log) 193 | send_message(f'[ERROR] - run_subfinder_httpx\n {str(e)[:33]}') 194 | # threading.Thread(target=run_nuclei, args=(target,)).start() 195 | 196 | 197 | def run_nuclei(target: str) -> None: 198 | print(f'Start scanning Nuclei for {target}...') 199 | log_msg = f'[Start Nuclei Scan] - {target}' 200 | write_logs(log_msg, target_log) 201 | if target.startswith('http') or target.startswith('https'): 202 | target_directory = "Target_Logs/Single_targets" 203 | command = f"nuclei -u {target} -as -nc" 204 | output_file_path = os.path.join(target_directory, f"{target.replace('://', '_')}_nuclei_scan_result.txt") 205 | else: 206 | target_directory = os.path.join("Target_Logs", target) 207 | target_file_path = os.path.join(target_directory, f"{target}_subdomains.txt") 208 | command = f"nuclei -l {target_file_path} -as -nc" 209 | output_file_path = os.path.join(target_directory, f"{target}_nuclei_scan_result.txt") 210 | 211 | send_message('Start scanning Nuclei...') 212 | list_vuln = '' 213 | is_vuln = False 214 | # Define the output file path 215 | 216 | 217 | # Run the command and capture its output incrementally 218 | # command = f"nuclei -l {target_file_path} -as -nc -o {output_file_path}" 219 | try: 220 | print(command) 221 | process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 222 | print('test123123') 223 | print(process) 224 | with open(output_file_path, "w") as f: 225 | for line in process.stdout: 226 | if '[critical]' in line: 227 | is_vuln = True 228 | list_vuln += (f'[Nuclei Scan] - {line.strip()}\n') 229 | vuln_msg = f'CRITICAL VULN for {target}: {line.strip()}' 230 | send_message(vuln_msg) 231 | if '[high]' in line: 232 | is_vuln = True 233 | list_vuln += (f'[Nuclei Scan] - {line.strip()}\n') 234 | vuln_msg = f'HIGH VULN for {target}: {line.strip()}' 235 | send_message(vuln_msg) 236 | if '[medium]' in line and not is_out_scope_vuln(line): 237 | is_vuln = True 238 | list_vuln += (f'[Nuclei Scan] - {line.strip()}\n') 239 | vuln_msg = f'MEDIUM VULN for {target}: {line.strip()}' 240 | send_message(vuln_msg) 241 | f.write(line) # Write each line to the file 242 | except subprocess.CalledProcessError as e: 243 | # Handle any errors if the command fails 244 | print(f"Error: {e}") 245 | log_msg = f'[__ERROR__] - {target} - {e}' 246 | return 247 | 248 | add_to_current_target_log(target, True) 249 | log_msg = f'[Done Nuclei Scan] - {target}' 250 | write_logs(log_msg,target_log) 251 | print(f'Done Scan Nuclei for {target}') 252 | if is_vuln: 253 | send_message(f'List Vuln from Medium to Critical:\n{list_vuln}') 254 | else: 255 | send_message(f'There is no Medium, High, or Critical vuln for the target {target}') 256 | 257 | def Add_Acunetix_targets(target: str, description :str, group_id=None) -> None: 258 | target_lists = [] 259 | target_directory = os.path.join("Target_Logs", target) 260 | target_file_path = os.path.join(target_directory, f"{target}_subdomains.txt") 261 | file = open(target_file_path,'r') 262 | for line in file: 263 | target = {} 264 | target['address'] = line.strip() 265 | target['description'] = description 266 | target_lists.append(target) 267 | 268 | Acunetix.AddTarget(target_lists, group_id) 269 | 270 | 271 | def compare_datetime_with_current(datetime_str: str) -> int: 272 | # Convert the datetime string to a datetime object 273 | given_datetime = datetime.fromisoformat(datetime_str[:-6]) 274 | current_datetime = datetime.utcnow() 275 | time_difference = current_datetime - given_datetime 276 | hours_difference = time_difference.total_seconds() / 3600 277 | 278 | return int(hours_difference) 279 | 280 | def replace_markdown_message(msg: str) -> str: 281 | msg = msg.replace('*', '\*') 282 | msg = msg.replace('_', '\_') 283 | msg = msg.replace('`', '\`') 284 | msg = msg.replace('[', '\[') 285 | msg = msg.replace('', '') 286 | msg = msg.replace('', '') 287 | msg = msg.replace('
  • ', '\n') 288 | msg = msg.replace('
  • ', '') 289 | msg = msg.replace('', '') 291 | msg = msg.replace('
    ', '\n') 292 | # msg = msg.replace(']', '\]') 293 | return msg 294 | 295 | def get_new_vuln_acunetix() -> None: 296 | global profile_id 297 | added_profile_id = Acunetix.CheckScanProfile() 298 | if added_profile_id != None: 299 | send_message_markdown(f'Created scan profile.') 300 | profile_id = added_profile_id 301 | is_scanning = False 302 | heavy_target_log = None 303 | global is_scan_dead 304 | # try: 305 | while True: 306 | try: 307 | is_scan_dead = False 308 | file = open("Logs/last_seen_acunetix.log") 309 | last_seen = file.read().strip() 310 | if config.NOTIFICATION: 311 | List_Latest_vulns = Acunetix.GetLatestVuln(last_seen) 312 | else: 313 | List_Latest_vulns = None 314 | in_process_target = Acunetix.GetInProcessTargets('processing') 315 | queued_target = Acunetix.GetInProcessTargets('queued') 316 | 317 | list_in_process_targets = in_process_target.get('scans') 318 | 319 | # Check time scan on the target and auto abort after 24/(pre-define) hours 320 | for target in list_in_process_targets: 321 | target_id = target.get('target_id') 322 | scan_id = target.get('scan_id') 323 | target_scan_info = Acunetix.GetTargetScanInfo(target_id=target_id) 324 | target_address = target_scan_info.get('scans')[0].get('target').get('address') 325 | target_start_date = target_scan_info.get('scans')[0].get('current_session').get('start_date') 326 | target_time_scan = compare_datetime_with_current(target_start_date) 327 | target_vulns = target_scan_info.get('scans')[0].get('current_session').get('severity_counts') 328 | 329 | try: 330 | heavy_target_log = json.loads(open(heavy_scan_acunetix_target_log,'r').read()).get('heavy_targets') 331 | with open(heavy_scan_acunetix_target_log, 'r') as file: 332 | data = json.load(file) 333 | 334 | if scan_id in data['heavy_targets']: 335 | is_remind = heavy_target_log.get(scan_id).get('is_remind') 336 | is_auto_stop = heavy_target_log.get(scan_id).get('is_auto_stop') 337 | except FileNotFoundError: 338 | heavy_target_log = None 339 | 340 | if target_time_scan > (config.SCAN_TIME - 1): 341 | Acunetix.add_to_heavy_scan_acunetix_target_log(scan_id=scan_id, target_id=target_id, is_remind=True, target_address=target_address) 342 | if target_time_scan % (config.SCAN_TIME - 1) == 0 and target_time_scan != 0: 343 | Acunetix.add_to_heavy_scan_acunetix_target_log(scan_id=scan_id, target_id=target_id, is_remind=False, target_address=target_address) 344 | heavy_target_log = json.loads(open(heavy_scan_acunetix_target_log,'r').read()).get('heavy_targets') 345 | # print(heavy_target_log) 346 | is_remind = heavy_target_log.get(scan_id).get('is_remind') 347 | is_auto_stop = heavy_target_log.get(scan_id).get('is_auto_stop') 348 | if is_remind and is_auto_stop: 349 | Acunetix.add_to_heavy_scan_acunetix_target_log(scan_id=scan_id, is_remind=False) 350 | msg = (f'target {target_address} is running for {config.SCAN_TIME - 1} hours now, consider to stop it using command: \n`\stop_scan_acunetix {scan_id}`' + 351 | f'\n\nThe tool will auto stop the scan on target after 1 hour from now, cancel auto stop on this target using command: \n`\\auto_abort_scan_false {scan_id}`' + 352 | f'\n\nList vuln:\n{json.dumps(target_vulns, indent=2)}') 353 | send_message_markdown(msg) 354 | write_logs(f'[Acunetix Scan] - Alert auto abort on target {target_address} after {target_time_scan} hour(s)', target_log) 355 | if target_time_scan >= config.SCAN_TIME and heavy_target_log != None: 356 | try: 357 | if heavy_target_log.get(scan_id).get('is_auto_stop'): 358 | msg = ( 359 | f'Stop Acunetix scan for {target_address}\nList vuln:\n{json.dumps(target_vulns, indent=2)}\n'+ 360 | f"For more detail, use the command:\n`/get_list_vulns {target_address}`" 361 | ) 362 | send_message_markdown(msg) 363 | Acunetix.StopScan(scan_id=scan_id) 364 | Acunetix.delete_to_heavy_scan_acunetix_target_log(scan_id) 365 | write_logs(f'[Acunetix Scan] - Auto abort scan target {target_address}', target_log) 366 | except Exception as e: 367 | print(f'[Error] - get_new_vuln_acunetix - Try to stop scan\n{e}') 368 | 369 | # prevent rate limit from Telegram 370 | time.sleep(1) 371 | # get the lastest vuln and send it to user 372 | if List_Latest_vulns != None: 373 | for vuln in List_Latest_vulns: 374 | 375 | vt_id = vuln.get('vt_id') 376 | if vt_id not in config.OUT_SCOPE_VULN_ACUNETIX: 377 | msg = '' 378 | affects_url = vuln.get('affects_url') 379 | confidence = vuln.get('confidence') 380 | vt_name = vuln.get('vt_name') 381 | vuln_id = vuln.get('vuln_id') 382 | severity = vuln.get('severity') 383 | txt_severity = '' 384 | if severity == 2: 385 | txt_severity = '[Medium]' 386 | if severity == 3: 387 | txt_severity = '[High]' 388 | if severity == 4: 389 | txt_severity = '[Critical]' 390 | 391 | vuln_details = Acunetix.get_vuln_details(vuln_id) 392 | msg = (f"{txt_severity} - {vt_name} : ({confidence} confidence)\n" + 393 | f"target: {affects_url}\n" + 394 | "----------------------\n" + 395 | "details:\n" + 396 | f"{replace_markdown_message(html.unescape(vuln_details.get('details')[:333]))}\n" + # prevent eror when the message too long and the bot can't handler it 397 | "----------------------\n" + 398 | "request:\n" + 399 | f"{vuln_details.get('request')}\n" + 400 | "----------------------\n" + 401 | "highlighted in response:\n" + 402 | f"{vuln_details.get('highlighted_text')[:1000]}" + 403 | "----------------------\n" 404 | ) 405 | msg = replace_markdown_message(msg) 406 | msg += f'To Stop receive noti for this vuln you can use command `/stop_vuln {vt_id}`' 407 | 408 | # except telegram.error.BadRequest: 409 | send_message_markdown(msg) 410 | # prevent rate limit from Telegram 411 | time.sleep(1) 412 | 413 | count = in_process_target.get('pagination').get('count') + queued_target.get('pagination').get('count') 414 | target_list = Acunetix.GetListTarget() 415 | if(len(target_list) != 0): 416 | is_scanning = True 417 | else: 418 | is_scanning = False 419 | 420 | if count < config.NUMBER_OF_TARGETS_SIMULTANEOUSLY and is_scanning: 421 | print(f'{count} - {config.NUMBER_OF_TARGETS_SIMULTANEOUSLY} - {is_scanning} - {is_scan_dead}') 422 | for i in range(config.NUMBER_OF_TARGETS_SIMULTANEOUSLY - count): 423 | try: 424 | target_id = target_list[i].get('target_id') 425 | address = target_list[i].get('address') 426 | Acunetix.StartScan(target_id, profile_id) 427 | msg = f'[Acunetix Scan] - Start scan target {address}' 428 | write_logs(msg, target_log) 429 | except Exception as e: 430 | print(f'[ERROR] - check line 418\n{e}...') 431 | # send_message(f'[ERROR] - check line 418{str(e)[:33]}...') 432 | pass 433 | time.sleep(1) 434 | if len(target_list) == 0 and is_scanning: 435 | is_scanning = False 436 | msg = 'Out of target for acunetix scan !' 437 | send_message(msg) 438 | write_logs(f'[Acunetix Scan] - Out of target !', target_log) 439 | 440 | time.sleep(60) 441 | except Exception as e: 442 | print(f'[ERROR]: get_new_vuln_acunetix function\n{e}') 443 | write_logs(f'[ERROR] - {str(e)[:33]}...', target_log) 444 | send_message(f'[ERROR] - {str(e)[:33]}...') 445 | is_scan_dead = True 446 | updater = telegram.ext.Updater(api_key, use_context=True) 447 | 448 | threading.Thread(target=get_new_vuln_acunetix).start() 449 | # write_logs('test 12312', 'Logs/Notes.txt') 450 | # run_nuclei('http://testphp.vulnweb.com/') -------------------------------------------------------------------------------- /Telegram_bot.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | import telegram.ext 4 | import config 5 | import AcuScan 6 | import Acunetix 7 | import logging 8 | import concurrent.futures 9 | import psutil 10 | import json 11 | import jwt 12 | import requests 13 | from concurrent.futures import ThreadPoolExecutor 14 | from telegram import ParseMode, Update, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardRemove 15 | from telegram.ext import Updater, CommandHandler, CallbackContext, CallbackQueryHandler, ConversationHandler, MessageHandler, Filters 16 | 17 | api_key = config.TELEGRAM_API_KEY 18 | chat_id = config.ALLOWED_USER_ID 19 | TOKEN_EXPIRATION = config.TOKEN_EXPIRATION 20 | SECRET_KEY = config.SECRET_KEY 21 | API_PORT = config.API_PORT 22 | targets_to_scan = [] 23 | scan_target = '' 24 | note_file_path = 'Logs/Notes.txt' 25 | host = config.HOST 26 | proxy_url = 'http://127.0.0.1:8080' 27 | proxies = { 28 | 'http': proxy_url, 29 | 'https': proxy_url 30 | } 31 | 32 | headers = {"X-Auth":config.ACUNETIX_API_KEY,"content-type": "application/json"} 33 | 34 | updater = telegram.ext.Updater(api_key, use_context=True) 35 | disp = updater.dispatcher 36 | 37 | # Create a ThreadPoolExecutor 38 | executor = concurrent.futures.ThreadPoolExecutor() 39 | 40 | user_tokens = {} 41 | 42 | def generate_token(user_id): 43 | payload = { 44 | 'user_id': user_id, 45 | } 46 | if TOKEN_EXPIRATION is not None: 47 | payload['exp'] = time.time() + TOKEN_EXPIRATION 48 | return jwt.encode(payload, SECRET_KEY, algorithm='HS256') 49 | 50 | def get_api_token(update: Update, context: CallbackContext) -> None: 51 | user_id = update.message.from_user.id 52 | token = generate_token(user_id) 53 | user_tokens[user_id] = token 54 | update.message.reply_text(f'Your API token: {token}') 55 | 56 | # Enable logging 57 | logging.basicConfig( 58 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO 59 | ) 60 | logging.getLogger("telegram.vendor.ptb_urllib3.urllib3").setLevel(logging.ERROR) 61 | logger = logging.getLogger(__name__) 62 | 63 | def error_handler(update, context): 64 | """Log any uncaught exceptions to the console.""" 65 | logging.error(msg="Exception while handling an update:", exc_info=context.error) 66 | 67 | def start_command(update: Update, context): 68 | update.message.reply_text('this is a test!') 69 | 70 | 71 | def add_target_command(update: Update, context): 72 | user_id = update.message.from_user.id 73 | if(chat_id != user_id): 74 | update.message.reply_text('Sorry, you are not authorized to run this command!') 75 | return 0 76 | target = context.args[0] if context.args else None 77 | if not target: 78 | update.message.reply_text("Please provide the the target.") 79 | return 80 | update.message.reply_text(f'Added target: {target}!', disable_web_page_preview=True) 81 | # update.message.reply_text(f'{msg}') 82 | # msg = AcuScan.run_subfinder_httpx(target) 83 | threading.Thread(target=AcuScan.run_subfinder_httpx, args=(target,)).start() 84 | 85 | 86 | def list_target_command(update: Update, context): 87 | user_id = update.message.from_user.id 88 | if(chat_id != user_id): 89 | update.message.reply_text('Sorry, you are not authorized to run this command!') 90 | return 0 91 | target_lists = 'List target not scan nuclei yet:\n' 92 | targets = AcuScan.get_targets_not_scan_nuclei() 93 | for target in targets: 94 | target_lists += f'`/start_scan {target}`\n' 95 | 96 | update.message.reply_text(f'{target_lists}\nUse command "/start\_scan " to start scan on this target subdomains', parse_mode=ParseMode.MARKDOWN) 97 | 98 | 99 | def print_subdomains_command(update: Update, context): 100 | user_id = update.message.from_user.id 101 | if(chat_id != user_id): 102 | update.message.reply_text('Sorry, you are not authorized to run this command!') 103 | return 0 104 | target = context.args[0] if context.args else None 105 | if not target: 106 | update.message.reply_text("Please provide the the target.") 107 | return 108 | count = 0 109 | list_subdomains = f'List subdomain of target {target}:\n' 110 | try: 111 | file_path = f'Target_logs/{target}/{target}_subdomains.txt' 112 | file_list_subdomains = open(file_path,"r") 113 | for line in file_list_subdomains: 114 | list_subdomains += f'{line.strip()}\n' 115 | count += 1 116 | update.message.reply_text(f'{list_subdomains}\nTotal: {count} subdomains.', disable_web_page_preview=True) 117 | except FileNotFoundError: 118 | update.message.reply_text('target not found! Add the target by using command /add_target', disable_web_page_preview=True) 119 | 120 | 121 | def start_scan_command(update: Update, context): 122 | user_id = update.message.from_user.id 123 | if chat_id != user_id: 124 | update.message.reply_text('Sorry, you are not authorized to run this command!') 125 | return 126 | 127 | target = context.args[0] if context.args else None 128 | if not target: 129 | update.message.reply_text("Please provide the the target to scan, it can be a wildcard target or single target.") 130 | return 131 | 132 | # start scan acunetix imediatly for single target (currently skipping scan nuclei) 133 | if target.startswith('http') or target.startswith('https'): 134 | acu_target = [ 135 | { 136 | "address" : f"{target}", 137 | "description" : "Single target auto scan" 138 | } 139 | ] 140 | 141 | target_id = Acunetix.AddTarget(acu_target,'')[0].get('target_id') 142 | Acunetix.StartScan(target_id, config.SCAN_PROFILE_ID) 143 | update.message.reply_text(f'Start acunetix scan on target {target}', disable_web_page_preview=True) 144 | msg = f'[Start Scan Acunetix] - {target}' 145 | AcuScan.write_logs(msg, AcuScan.target_log) 146 | 147 | else: 148 | targets = AcuScan.get_targets_not_scan_nuclei() 149 | 150 | if target not in targets: 151 | update.message.reply_text(f'Target {target} is not in the list. Add target to the list using command /add_target') 152 | return 153 | 154 | # Submit the scan task to the ThreadPoolExecutor 155 | update.message.reply_text(f'Scan started for target {target}.', disable_web_page_preview=True) 156 | # fix this in the future 157 | # executor.submit(AcuScan.run_nuclei, target) 158 | 159 | 160 | def set_targets_command(update: Update, context): 161 | user_id = update.message.from_user.id 162 | if chat_id != user_id: 163 | update.message.reply_text('Sorry, you are not authorized to run this command!') 164 | return 165 | 166 | number_of_targets = context.args[0] if context.args else None 167 | if not number_of_targets: 168 | update.message.reply_text("Please provide the number of targets you wanto scan at the same time.") 169 | return 170 | 171 | try: 172 | config.NUMBER_OF_TARGETS_SIMULTANEOUSLY = int(number_of_targets) 173 | except: 174 | update.message.reply_text(f'Fail to set number of targets simultaneously to {number_of_targets}') 175 | 176 | update.message.reply_text(f'Set number of targets simultaneously for acunetix to: {config.NUMBER_OF_TARGETS_SIMULTANEOUSLY}') 177 | msg = f'[Config] - Set number of targets simultaneously for acunetix to: {config.NUMBER_OF_TARGETS_SIMULTANEOUSLY}' 178 | AcuScan.write_logs(msg, AcuScan.target_log) 179 | 180 | 181 | def stop_vuln_command(update: Update, context): 182 | user_id = update.message.from_user.id 183 | if chat_id != user_id: 184 | update.message.reply_text('Sorry, you are not authorized to run this command!') 185 | return 186 | 187 | vuln_id = context.args[0] if context.args else None 188 | if not vuln_id: 189 | update.message.reply_text("Please provide Vulnerability ID.") 190 | return 191 | 192 | config.OUT_SCOPE_VULN_ACUNETIX.append(vuln_id) 193 | update.message.reply_text(f'Stop receiving noti for vuln {vuln_id}') 194 | msg = f"[Config] - Stop receive noti for vuln {vuln_id}" 195 | AcuScan.write_logs(msg, AcuScan.target_log) 196 | 197 | 198 | def auto_abort_scan_false_command(update: Update, context): 199 | user_id = update.message.from_user.id 200 | if chat_id != user_id: 201 | update.message.reply_text('Sorry, you are not authorized to run this command!') 202 | return 203 | 204 | target = context.args[0] if context.args else None 205 | if not target: 206 | update.message.reply_text("Please provide target address or scan ID.") 207 | return 208 | 209 | if target.startswith('http://') or target.startswith('https://'): 210 | Acunetix.add_to_heavy_scan_acunetix_target_log(target_address=target, is_auto_stop=False) 211 | else: 212 | Acunetix.add_to_heavy_scan_acunetix_target_log(scan_id=target, is_auto_stop=False) 213 | 214 | msg = f'To set the auto stop back on this target use the command `\\auto_abort_scan_true {target}`' 215 | update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN) 216 | 217 | 218 | def auto_abort_scan_true_command(update: Update, context): 219 | user_id = update.message.from_user.id 220 | if chat_id != user_id: 221 | update.message.reply_text('Sorry, you are not authorized to run this command!') 222 | return 223 | 224 | target = context.args[0] if context.args else None 225 | if not target: 226 | update.message.reply_text("Please provide target address or scan ID.") 227 | return 228 | 229 | if target.startswith('http://') or target.startswith('https://'): 230 | Acunetix.add_to_heavy_scan_acunetix_target_log(target_address=target, is_auto_stop=True) 231 | else: 232 | Acunetix.add_to_heavy_scan_acunetix_target_log(scan_id=target, is_auto_stop=True) 233 | 234 | msg = f'Set the auto stop scan acunetix for target: {target}' 235 | update.message.reply_text(msg) 236 | 237 | 238 | def ram_command(update: Update, context): 239 | user_id = update.message.from_user.id 240 | if chat_id != user_id: 241 | update.message.reply_text('Sorry, you are not authorized to run this command!') 242 | return 243 | 244 | # Get system memory usage 245 | mem = psutil.virtual_memory() 246 | msg = f"Memory Usage Percentage: {mem.percent}%" 247 | update.message.reply_text(msg) 248 | 249 | def get_processing_targets_command(update: Update, context): 250 | user_id = update.message.from_user.id 251 | if chat_id != user_id: 252 | update.message.reply_text('Sorry, you are not authorized to run this command!') 253 | return 254 | in_process_target = Acunetix.GetInProcessTargets('processing') 255 | list_in_process_targets = in_process_target.get('scans') 256 | target_left = Acunetix.GetTargetsLeft() 257 | processing_target = f'{len(list_in_process_targets)} / {config.NUMBER_OF_TARGETS_SIMULTANEOUSLY} simultaneously targets:\nTargets left: {target_left}\nScan time: {config.SCAN_TIME}\n\n' 258 | 259 | for target in list_in_process_targets: 260 | target_address = target.get('target').get('address') 261 | scan_id = target.get('scan_id') 262 | start_date = target.get('current_session').get('start_date') 263 | time_scaned = AcuScan.compare_datetime_with_current(start_date) 264 | vulns = json.dumps(target.get('current_session').get('severity_counts'), indent=2) 265 | processing_target += ( 266 | f'target: {target_address}\n' + 267 | f'Scan id: `{scan_id}`\n' + 268 | f'Time scaned: {time_scaned}\n' + 269 | f'List vuln:\n' + 270 | f'{vulns}\n\n' + 271 | '==========\n\n' 272 | ) 273 | update.message.reply_text(processing_target, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 274 | 275 | 276 | def stop_scan_acunetix_command(update: Update, context: CallbackContext) -> None: 277 | user_id = update.message.from_user.id 278 | if chat_id != user_id: 279 | update.message.reply_text('Sorry, you are not authorized to run this command!') 280 | return 281 | # Extract the scan ID from the command 282 | scan_id = context.args[0] if context.args else None 283 | 284 | if not scan_id: 285 | update.message.reply_text("Please provide a scan ID.") 286 | return 287 | 288 | # Prepare the inline keyboard with two buttons: Yes and No 289 | keyboard = [ 290 | [InlineKeyboardButton("Yes", callback_data=f"stop_scan_yes:{scan_id}"), 291 | InlineKeyboardButton("No", callback_data="stop_scan_no")] 292 | ] 293 | reply_markup = InlineKeyboardMarkup(keyboard) 294 | 295 | # Ask the user for confirmation using the inline keyboard 296 | update.message.reply_text("Are you sure you want to stop the scan?", reply_markup=reply_markup) 297 | 298 | 299 | # Define the callback function for handling inline button clicks 300 | def button_click_stop_scan(update: Update, context: CallbackContext) -> None: 301 | query = update.callback_query 302 | data = query.data.split(':') 303 | action = data[0] 304 | scan_id = data[1] if len(data) > 1 else None 305 | 306 | if action == 'stop_scan_yes': 307 | # Implement logic to stop the scan using the provided scan_id 308 | Acunetix.StopScan(scan_id=scan_id) 309 | Acunetix.delete_to_heavy_scan_acunetix_target_log(scan_id) 310 | address = Acunetix.GetDetailFromScanId(scan_id).get('target').get('address') 311 | query.edit_message_text(f'Successfull abort scan {address} - {scan_id}') 312 | msg = f'[Acunetix Scan] - Manual abort scan target {address}' 313 | AcuScan.write_logs(msg, AcuScan.target_log) 314 | query.edit_message_text(f"Stopping scan on target {address}...", disable_web_page_preview=True) 315 | else: 316 | query.edit_message_text("Cancel stop scan on the target.") 317 | 318 | # def stop_scan_acunetix_command(update: Update, context: CallbackContext) -> None: 319 | # user_id = update.message.from_user.id 320 | # if chat_id != user_id: 321 | # update.message.reply_text('Sorry, you are not authorized to run this command!') 322 | # return 323 | # # Extract the scan ID from the command 324 | # scan_id = context.args[0] if context.args else None 325 | 326 | # if not scan_id: 327 | # update.message.reply_text("Please provide a scan ID.") 328 | # return 329 | 330 | def get_list_vulns_command(update: Update, context: CallbackContext) -> None: 331 | user_id = update.message.from_user.id 332 | if chat_id != user_id: 333 | update.message.reply_text('Sorry, you are not authorized to run this command!') 334 | return 335 | # Extract the scan ID from the command 336 | update.message.reply_text('Getting list vulns. Wait for few second...') 337 | # serverity = context.args[0] if context.args else None 338 | # targets = context.args[1] if context.args else None 339 | 340 | args=update.message.text[15:] 341 | args = args.split() 342 | serverity = args[0] if len(args) > 0 else None 343 | targets= args[1] if len(args) > 1 else None 344 | if serverity != None and targets == None and (serverity.startswith('https://') or serverity.startswith('http://')): 345 | targets = serverity 346 | serverity = '2,3,4' 347 | # if serverity != None and targets == None and not (serverity.startswith('https://') or serverity.startswith('http://')) : 348 | # list_target_id_str = serverity 349 | # try: 350 | # print(serverity) 351 | list_targets_address = [] 352 | # response = requests.get(f'{host}/api/v1/targets?l=100&q=text_search:*{targets.strip()};', headers=headers, verify=False, proxies=proxies) 353 | 354 | if not serverity and not targets: 355 | query = f'severity:2,3,4;target_id:;status:!ignored;status:!fixed;&s=severity:desc' 356 | else: 357 | if not serverity: 358 | serverity = '' 359 | if not targets: 360 | list_target_id_str = '' 361 | else: 362 | list_targets_address = targets.split(',') 363 | list_target_id = [] 364 | for target in list_targets_address: 365 | response = requests.get(f'{host}/api/v1/targets?l=100&q=text_search:*{target.strip()};', headers=headers, verify=False) 366 | target_info = json.loads(response.text).get('targets')[0] 367 | target_id = target_info.get('target_id') 368 | list_target_id.append(target_id) 369 | 370 | list_target_id_str = '' 371 | try: 372 | for target_id in list_target_id: 373 | list_target_id_str += f'{target_id.strip()},' 374 | except: 375 | pass 376 | query = f'severity:{serverity};target_id:{list_target_id_str};status:!ignored;status:!fixed;&s=severity:desc' 377 | 378 | response = requests.get(f'{host}/api/v1/vulnerabilities?l=14&q={query}', headers=headers, verify=False) 379 | json_response = json.loads(response.text) 380 | if response.status_code == 400: 381 | msg = f'400 Bad Request\nYour querry: {query}\n{json_response}' 382 | update.message.reply_text(msg, disable_web_page_preview=True) 383 | return 0 384 | count_vuln = str(json_response.get('pagination').get('count')) 385 | List_vulns = json_response.get('vulnerabilities') 386 | if len(list_targets_address) != 0: 387 | List_vuln_str = f'Vulnerability of: {list_targets_address}\nTotal: {len(List_vulns)} vulns\n\n' 388 | else: 389 | List_vuln_str = f'All Vulnerability found\nTotal: {count_vuln} vulns\n\n' 390 | 391 | for vuln in List_vulns: 392 | vt_id = vuln.get('vt_id') 393 | if vt_id not in config.OUT_SCOPE_VULN_ACUNETIX: 394 | vuln_severity = vuln.get('severity') 395 | txt_severity = '' 396 | if vuln_severity == 1: 397 | txt_severity = '[Lows]' 398 | if vuln_severity == 2: 399 | txt_severity = '[Medium]' 400 | if vuln_severity == 3: 401 | txt_severity = '[High]' 402 | if vuln_severity == 4: 403 | txt_severity = '[Critical]' 404 | vuln_detail = ( 405 | f"*{txt_severity} - {vuln.get('vt_name')} : ({vuln.get('confidence')} confidence)*\n" + 406 | f"Target: {vuln.get('affects_url')}\n" + 407 | f"For more detail: `/vuln_detail {vuln.get('vuln_id')}`\n" + 408 | f"To Stop receive noti for this vuln you can use command `/stop_vuln {vuln.get('vt_id')}`\n" + 409 | "----------\n" 410 | ) 411 | List_vuln_str += vuln_detail 412 | # print(List_vuln_str) 413 | update.message.reply_text(List_vuln_str, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 414 | print(List_vuln_str) 415 | # except Exception as e: 416 | # print(f'[ERROR]: get_list_vulns function\n{e}') 417 | # AcuScan.write_logs(f'[ERROR]: get_list_vulns function\n {str(e)[:33]}', log_path=AcuScan.target_log) 418 | # update.message.reply_text(f'Some thing went wrong in get_list_vulns. Error message: {str(e)[:333]}') 419 | 420 | def convert_serverity(serverity: int): 421 | if serverity == 0: 422 | txt_severity = '[Information]' 423 | if serverity == 1: 424 | txt_severity = '[Lows]' 425 | if serverity == 2: 426 | txt_severity = '[Medium]' 427 | if serverity == 3: 428 | txt_severity = '[High]' 429 | if serverity == 4: 430 | txt_severity = '[Critical]' 431 | return txt_severity 432 | 433 | def vuln_detail_command(update: Update, context): 434 | user_id = update.message.from_user.id 435 | if chat_id != user_id: 436 | update.message.reply_text('Sorry, you are not authorized to run this command!') 437 | return 438 | 439 | vuln_id = context.args[0] if context.args else None 440 | if not vuln_id: 441 | update.message.reply_text("Please provide a vuln ID") 442 | return 443 | 444 | response = requests.get(f'{host}/api/v1/vulnerabilities/{vuln_id}', headers=headers, verify=False) 445 | json_response = json.loads(response.text) 446 | 447 | references = json_response.get('references') 448 | references_txt = '' 449 | for reference in references: 450 | references_txt += f"[{reference.get('rel')}]({reference.get('href')})\n" 451 | pass 452 | 453 | vuln_severity = json_response.get('severity') 454 | txt_severity = convert_serverity(vuln_severity) 455 | 456 | 457 | vuln_details = Acunetix.get_vuln_details(vuln_id) 458 | msg = (f"*{txt_severity} - {json_response.get('vt_name')} : ({json_response.get('confidence')} confidence)*\n" + 459 | f"target: {json_response.get('affects_url')}\n" + 460 | "----------------------\n" + 461 | "details:\n" + 462 | f"CVSS Score: {json_response.get('cvss_score')}\n" + 463 | f"{json_response.get('cvss3')}\n\n" + 464 | f"{AcuScan.replace_markdown_message(AcuScan.html.unescape(vuln_details.get('details')[:333]))}...\n" + 465 | "----------------------\n" + 466 | "request:\n" + 467 | f"{AcuScan.replace_markdown_message(vuln_details.get('request'))}\n" + 468 | "----------------------\n" + 469 | "highlighted in response:\n" + 470 | f"{AcuScan.replace_markdown_message(vuln_details.get('highlighted_text'))}" + 471 | "----------------------\n" 472 | f"references: \n{references_txt}" + 473 | "----------------------\n" 474 | ) 475 | update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 476 | 477 | 478 | def vuln_type_command(update: Update, context): 479 | user_id = update.message.from_user.id 480 | if chat_id != user_id: 481 | update.message.reply_text('Sorry, you are not authorized to run this command!') 482 | return 483 | 484 | vuln_type = int(context.args[0]) if context.args else None 485 | if not vuln_type: 486 | update.message.reply_text('Please provide a vuln type (1-4).') 487 | return 0 488 | 489 | txt_severity = convert_serverity(vuln_type) 490 | 491 | list_msg = [] 492 | msg = f'[{txt_severity}]:\n\n' 493 | for item in Acunetix.GetVulnType(): 494 | if item.get('severity') == vuln_type: 495 | new_vuln_type = f"{AcuScan.replace_markdown_message(item.get('name'))}\n`/search_by_vuln_type {item.get('vt_id')}`\n`/stop_vuln {item.get('vt_id')}`\n\n" 496 | if len(msg + new_vuln_type) > 4096: 497 | list_msg.append(msg) 498 | msg = '' 499 | msg += new_vuln_type 500 | list_msg.append(msg) 501 | 502 | for msg in list_msg: 503 | update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 504 | time.sleep(1) 505 | 506 | 507 | def search_by_vuln_type_command (update: Update, context): 508 | user_id = update.message.from_user.id 509 | if chat_id != user_id: 510 | update.message.reply_text('Sorry, you are not authorized to run this command!') 511 | return 512 | 513 | vuln_type_id = context.args[0] if context.args else None 514 | if not vuln_type_id: 515 | update.message.reply_text('Please provide a vuln type (1-4).') 516 | return 0 517 | 518 | list_vulns = Acunetix.GetLatestVuln(vt_id=vuln_type_id, severity='0,1,2,3,4') 519 | vt_name = list_vulns[0].get('vt_name') 520 | list_msg = [] 521 | msg = f"List Vulnerabilities for {AcuScan.replace_markdown_message(vt_name)}:\n\n" 522 | for item in list_vulns: 523 | new_vuln_msg = ( 524 | f"Affects URL: {AcuScan.replace_markdown_message(item.get('affects_url'))} ({item.get('confidence')} confidence)\n" + 525 | f"`/vuln_detail {AcuScan.replace_markdown_message(item.get('vuln_id'))}`\n\n" 526 | ) 527 | if len(msg + new_vuln_msg) > 4096: 528 | list_msg.append(msg) 529 | msg = '' 530 | msg += new_vuln_msg 531 | list_msg.append(msg) 532 | for msg in list_msg: 533 | update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 534 | time.sleep(1) 535 | 536 | 537 | def help_command(update: Update, context): 538 | user_id = update.message.from_user.id 539 | if chat_id != user_id: 540 | update.message.reply_text('Sorry, you are not authorized to run this command!') 541 | return 542 | update 543 | # Make sure _ is escape ! 544 | help_msg = ( 545 | "`/start`" + " - just a start !\n" + 546 | "`/help`" + " - help !\n" + 547 | "`/add_target`" + " - add wildcard target to find it subdomains.\n" + 548 | "`/start_scan`" + " - start scan on wildcard / single target\n" + 549 | "`/set_targets`" + " - Set number of targets simultaneously scan acunetix\n" + 550 | "`/list_target`" + " - list all the target add by /add\_target that not been scan nuclei\n" + 551 | "`/print_subdomains`" + " - print subdomains of target\n" + 552 | "`/stop_vuln`" + " - add vuln to list out scope\n" + 553 | "`/auto_abort_scan_true`" + " - set auto abort after 9 hours for target\n" + 554 | "`/auto_abort_scan_false`" + " - unset auto abort after 9 hours for target\n" + 555 | "`/ram`" + " - show system memory usage\n" + 556 | "`/get_processing_targets`" + " - list all the target have status proccessing acunetix\n" + 557 | "`/stop_scan_acunetix`" + " - stop an acunetix scan by scan\_id\n" + 558 | "`/get_list_vulns`" + " - get all the vuln by filter eg: '/get\_list\_vulns 2,3,4 https://example.com,https://google.com'\n" + 559 | "`/vuln_detail`" + " - get vuln detail by vuln id\n" + 560 | "`/vuln_type`" + " - get all scanned vulns by severity (1-4)\n" + 561 | "`/search_vuln`" + " - search for vuln has been scaned\n" + 562 | "`/manual_activate_auto_scan`" + " - Manual activate auto scan after it dead. Not sure if this work :)\n" + 563 | "`/notification`" + " - Enable or disable new vulnerabilities notifications from the bot\n" + 564 | "`/get_api_token`" + " - generate a new API token that can be used for authenticating requests to the associated API server" 565 | ) 566 | update.message.reply_text(help_msg, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 567 | 568 | 569 | def read_note_command(update: Update, context): 570 | user_id = update.message.from_user.id 571 | if chat_id != user_id: 572 | update.message.reply_text('Sorry, you are not authorized to run this command!') 573 | return 574 | 575 | note = context.args[0] if context.args else None 576 | try: 577 | Notes = '' 578 | with open(note_file_path, 'r') as file: 579 | List_notes = file.readlines() 580 | if not note: 581 | for item in List_notes: 582 | Notes += f'{item}' 583 | else: 584 | try: 585 | for num in range(int(note)): 586 | Notes += f'{List_notes[num]}' 587 | except IndexError as e: 588 | print(type(str(e))) 589 | print(f'{str(e)[:33]}...') 590 | update.message.reply_text(Notes, disable_web_page_preview=True) 591 | except FileNotFoundError as e: 592 | update.message.reply_text('There are currently no notes recorded') 593 | 594 | 595 | def note_command(update: Update, context): 596 | update.message.reply_text("Please enter your note:") 597 | return 1 598 | 599 | def save_note(update: Update, context): 600 | note = update.message.text 601 | if not note: 602 | update.message.reply_text("The note is empty. Please enter a valid note.") 603 | return 0 604 | AcuScan.write_logs(f'[Note]: {note}', note_file_path) 605 | update.message.reply_text("Note saved successfully.", reply_markup=ReplyKeyboardRemove()) 606 | return ConversationHandler.END 607 | 608 | 609 | def manual_activate_auto_scan_command(update: Update, context): 610 | if AcuScan.is_scan_dead: 611 | threading.Thread(target=AcuScan.get_new_vuln_acunetix).start() 612 | update.message.reply_text('Manual activate auto scan after it dead. Not sure if this work, good luck :v') 613 | else: 614 | update.message.reply_text('the auto scan function is not dead. OR... Something wrong here =))') 615 | 616 | 617 | def search_vuln_command(update: Update, context): 618 | user_id = update.message.from_user.id 619 | if chat_id != user_id: 620 | update.message.reply_text('Sorry, you are not authorized to run this command!') 621 | return 622 | 623 | query = context.args[0] if context.args else None 624 | if not query: 625 | query = '' 626 | 627 | response = requests.get(f'{host}/api/v1/vulnerability_types?q=severity:{query};status:!ignored;status:!fixed', headers=headers, verify=False) 628 | json_response = json.loads(response.text) 629 | vulnerability_types = json_response.get('vulnerability_types') 630 | list_msg = [] 631 | msg = f'List vulns for type {query}:\n\n' 632 | for vuln_type in vulnerability_types: 633 | new_msg = ( 634 | f"[{convert_serverity(vuln_type.get('severity'))}] - {AcuScan.replace_markdown_message(vuln_type.get('name'))}\n" + 635 | f"`/search_by_vuln_type {vuln_type.get('vt_id')}`\n\n" 636 | ) 637 | if (len(msg) + len(new_msg) > 4096): 638 | list_msg.append(msg) 639 | msg = '' 640 | msg += new_msg 641 | list_msg.append(msg) 642 | for msg in list_msg: 643 | update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True) 644 | time.sleep(1) 645 | 646 | def notification_command(update: Update, context): 647 | user_id = update.message.from_user.id 648 | if(chat_id != user_id): 649 | update.message.reply_text('Sorry, you are not authorized to run this command!') 650 | return 0 651 | status = context.args[0] if context.args else None 652 | if not status: 653 | update.message.reply_text("Please provide `True` or `False` to set the status of notification.") 654 | return 655 | status = status.strip().lower() 656 | if status == 'true': 657 | status = True 658 | elif status == 'false': 659 | status = False 660 | else: 661 | update.message.reply_text(f"Invalid input argument '{status}'. Expected 'True' or 'False'. Current notification status: {config.NOTIFICATION}") 662 | 663 | if isinstance(status, bool): 664 | config.NOTIFICATION = status 665 | update.message.reply_text(f'Set notification to {status}!') 666 | 667 | def run_flask_api(): 668 | from api import app # Import the Flask app object from api.py 669 | app.run(host='0.0.0.0', port=API_PORT) 670 | 671 | def run_telegram_bot(): 672 | 673 | disp.add_handler(telegram.ext.CommandHandler("start", start_command)) 674 | disp.add_handler(telegram.ext.CommandHandler("help", help_command)) 675 | disp.add_handler(telegram.ext.CommandHandler("add_target", add_target_command)) 676 | disp.add_handler(telegram.ext.CommandHandler("start_scan", start_scan_command)) 677 | disp.add_handler(telegram.ext.CommandHandler("set_targets", set_targets_command)) 678 | disp.add_handler(telegram.ext.CommandHandler("list_target", list_target_command)) 679 | disp.add_handler(telegram.ext.CommandHandler("print_subdomains", print_subdomains_command)) 680 | disp.add_handler(telegram.ext.CommandHandler("stop_vuln", stop_vuln_command)) 681 | disp.add_handler(telegram.ext.CommandHandler("auto_abort_scan_true", auto_abort_scan_true_command)) 682 | disp.add_handler(telegram.ext.CommandHandler("auto_abort_scan_false", auto_abort_scan_false_command)) 683 | disp.add_handler(telegram.ext.CommandHandler("ram", ram_command)) 684 | disp.add_handler(telegram.ext.CommandHandler("get_processing_targets", get_processing_targets_command)) 685 | disp.add_handler(telegram.ext.CommandHandler("stop_scan_acunetix", stop_scan_acunetix_command)) 686 | disp.add_handler(telegram.ext.CommandHandler("get_list_vulns", get_list_vulns_command)) 687 | disp.add_handler(telegram.ext.CommandHandler("vuln_detail", vuln_detail_command)) 688 | disp.add_handler(telegram.ext.CommandHandler("vuln_type", vuln_type_command)) 689 | disp.add_handler(telegram.ext.CommandHandler("search_by_vuln_type", search_by_vuln_type_command)) 690 | disp.add_handler(telegram.ext.CommandHandler("read_note", read_note_command)) 691 | disp.add_handler(telegram.ext.CommandHandler("manual_activate_auto_scan", manual_activate_auto_scan_command)) 692 | disp.add_handler(telegram.ext.CommandHandler("search_vuln", search_vuln_command)) 693 | disp.add_handler(telegram.ext.CommandHandler("get_api_token", get_api_token)) 694 | disp.add_handler(telegram.ext.CommandHandler("notification", notification_command)) 695 | 696 | disp.add_handler(CallbackQueryHandler(button_click_stop_scan)) 697 | note_handler = ConversationHandler( 698 | entry_points=[CommandHandler("note", note_command)], 699 | states={ 700 | 1: [MessageHandler(Filters.text & ~Filters.command, save_note)] 701 | }, 702 | fallbacks=[] 703 | ) 704 | disp.add_handler(note_handler) 705 | disp.add_error_handler(error_handler) 706 | 707 | updater.start_polling() 708 | updater.idle() 709 | 710 | 711 | # threading.Thread(target=AcuScan.get_new_vuln_acunetix).start() 712 | 713 | def main() -> None: 714 | if config.API_SERVER: 715 | # Create a ThreadPoolExecutor to run both the Telegram bot and the Flask API 716 | with ThreadPoolExecutor(max_workers=2) as executor: 717 | executor.submit(run_telegram_bot) # Start the Telegram bot 718 | executor.submit(run_flask_api) # Start the Flask API 719 | else: 720 | run_telegram_bot() # Only run the Telegram bot 721 | 722 | if __name__ == "__main__": 723 | main() 724 | 725 | help = """ 726 | /start: just a start ! 727 | /note: write a note 728 | /read_note: retrive note, can add arg as number of line 729 | /help: help ! 730 | /add_target: add wildcard target to find it subdomains. 731 | /start_scan: start scan on wildcard / single target 732 | /set_targets: Set number of targets simultaneously scan acunetix 733 | /list_target: list all the target add by /add_target that not been scan nuclei 734 | /print_subdomains: print subdomains of target 735 | /stop_vuln: add vuln to list out of scope 736 | /auto_abort_scan_true: set auto abort after 9 hours for target 737 | /auto_abort_scan_false: unset auto abort after 9 hours for target 738 | /ram: show system memory usage 739 | /get_processing_targets: list all the target have status proccessing acunetix 740 | /stop_scan_acunetix: stop an acunetix scan by scan_id 741 | /get_list_vulns: get all the vuln by filter eg: /get_list_vulns 2,3,4 https://example.com, https://google.com 742 | /vuln_detail: get vuln detail by vuln id 743 | /vuln_type: get all scanned vulns by severity (1-4) 744 | /search_vuln: search for vuln has been scaned 745 | /manual_activate_auto_scan: Manual activate auto scan after it dead. Not sure if this work :) 746 | /notification: Enable or disable new vulnerabilities notifications from the bot 747 | /get_api_token: generate a new API token that can be used for authenticating requests to the associated API server 748 | """ 749 | 750 | --------------------------------------------------------------------------------