├── Dockerfile
├── LICENSE.txt
├── README.md
├── cli
└── chiasmodon_cli.py
├── pychiasmodon.py
├── requirements.txt
└── setup.py
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.11-alpine
2 |
3 | COPY . .
4 | RUN python setup.py install
5 |
6 | ENTRYPOINT [ "python3","/cli/chiasmodon_cli.py" ]
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Chiasmodon
3 |
4 | [](https://asciinema.org/a/QrEtBLFMQrjU1sjRjcgTdo41m)
5 |
6 |
7 |
8 | Chiasmodon is an OSINT tool that allows users to gather information from various sources and conduct targeted searches based on domains, Google Play applications, email addresses, IP addresses, organizations, URLs, and more. It provides comprehensive scanning capabilities, customizable output formats, and additional options for enhanced data analysis and customization.
9 |
10 |
11 | ## ✨Features
12 |
13 | - [x] **🌐Domain**: Conduct targeted searches by specifying a domain name to gather relevant information related to the domain.
14 | - [x] **🎮Google Play Application**: Search for information related to a specific application on the Google Play Store by providing the application ID.
15 | - [x] **✉️Email, 👤Username, 🔒Password**: Conduct searches based on email, username, or password to identify potential security risks or compromised credentials.
16 | - [x] **🔍 IP Address**: Perform searches using an IP address to gather information such as geolocation, associated domain names, and historical data.
17 | - [x] **🌍 CIDR**: Search for information related to a specified CIDR (Classless Inter-Domain Routing) block, including IP range details and associated networks.
18 | - [x] **🔢 ASN**: Retrieve information about an Autonomous System Number (ASN), including its owner, associated IP ranges, and network details.
19 | - [x] **🔌 Port**: Search for information about a specific port number, including its common usage, associated services, and potential vulnerabilities.
20 | - [x] **🌐 ISP**: Conduct searches based on an Internet Service Provider (ISP) name to gather information about the ISP, its services, and associated IP ranges.
21 | - [x] **🏢 Organization (ORG)**: Search for information related to a specific organization or company, including its contact details, associated domains, and network infrastructure.
22 | - [x] **🔗 URL Path**: Perform searches based on a specific URL path to gather information about the path, its content, and potential security risks.
23 | - [x] **📞 Phone**: Conduct searches using a phone number to gather information such as the associated owner, location, and any available public records.
24 | - [x] **🔍Scan**: Perform a comprehensive scan on a given company domain name in one click, including finding
25 | - Related companies.
26 | - App applications.
27 | - Ips (`Port, Org, Isp, Asn`).
28 | - Subdomains.
29 | - Client credentials (`Email, Username, Password`).
30 | - Employee credentials (`Email, Username, Password`)
31 | - URLs (`Domain/IP, Port, Endpoint`)
32 |
33 | - [X] **🌍Country**: Sort and filter search results by country to gain insights into the geographic distribution of the identified information.
34 | - [x] **📋Output Customization**: Choose the desired output format (text, JSON, or CSV) and specify the filename to save the search results.
35 | - [x] **⚙️Additional Options**: The tool offers various additional options, such as viewing different result types (credentials, URLs, subdomains, emails, passwords, usernames, or applications), setting API tokens, specifying timeouts, limiting results, and more.
36 |
37 | ## 🚀Comming soon
38 |
39 | - **🏢Company Name**: We understand the importance of comprehensive company research. In our upcoming release, you'll be able to search by company name and access a wide range of documents associated with that company. This feature will provide you with a convenient and efficient way to gather crucial information, such as legal documents, financial reports, and other relevant records.
40 |
41 | - **👤Face (Photo)**: Visual data is a powerful tool, and we are excited to introduce our advanced facial recognition feature. With "Search by Face (Photo)," you can upload an image containing a face and leverage cutting-edge technology to identify and match individuals across various data sources. This will allow you to gather valuable information, such as social media profiles, online presence, and potential connections, all through the power of facial recognition.
42 |
43 | ## Why Chiasmodon name ?
44 | Chiasmodon niger is a species of deep sea fish in the family Chiasmodontidae. It is known for its ability to **swallow fish larger than itself**. and so do we. 😉
45 | 
46 |
47 | ## 🔑 Subscription
48 | Join us today and unlock the potential of our cutting-edge OSINT tool. Contact https://t.me/Chiasmod0n on Telegram to subscribe and start harnessing the power of Chiasmodon for your domain investigations.
49 |
50 | ## ⬇️Install
51 | ```bash
52 | $ pip install chiasmodon
53 | ```
54 | Only for linux 👇
55 | ```bash
56 | $ activate-global-python-argcomplete
57 | ```
58 | ## 💻Usage
59 | Chiasmodon provides a flexible and user-friendly command-line interface and python library. Here are some examples to demonstrate its usage:
60 |
61 |
62 | ```
63 | usage: chiasmodon_cli.py [-h]
64 | [-m {cred.username,cred.password,cred.email,cred.phone,cred.email.domain,cred.country,domain,domain.all,ip,ip.asn,ip.isp,ip.org,ip.port,ip.country,app.id,app.name,app.domain,url.path,url.port}]
65 | [-vt {full,cred,url,email,phone,password,username,app,domain,ip,related,subdomain}] [-s] [-sr SCAN_RELATED]
66 | [-ss SCAN_SUBDOMAINS] [-sa SCAN_APPS] [-si SCAN_IPS] [-sc SCAN_CLIENTS] [-se SCAN_EMPLOYEES] [-o OUTPUT]
67 | [-ot {text,json,csv}] [-t TIMEOUT] [-l LIMIT] [-nc] [-lv] [-lm] [--init INIT] [-v]
68 | query
69 |
70 | Chiasmodon CLI
71 |
72 | positional arguments:
73 | query query argument
74 |
75 | options:
76 | -h, --help show this help message and exit
77 | -m {cred.username,cred.password,cred.email,cred.phone,cred.email.domain,cred.country,domain,domain.all,ip,ip.asn,ip.isp,ip.org,ip.port,ip.country,app.id,app.name,app.domain,url.path,url.port}, --method {cred.username,cred.password,cred.email,cred.phone,cred.email.domain,cred.country,domain,domain.all,ip,ip.asn,ip.isp,ip.org,ip.port,ip.country,app.id,app.name,app.domain,url.path,url.port}
78 | method to search by it,default is "domain".
79 | -vt {full,cred,url,email,phone,password,username,app,domain,ip,related,subdomain}, --view-type {full,cred,url,email,phone,password,username,app,domain,ip,related,subdomain}
80 | type view the result default is "full".
81 | -s, --scan scan the company domain (Related company, Clients, Employees, Company ASNs, Company Apps).
82 | -sr SCAN_RELATED, --scan-related SCAN_RELATED
83 | Run related scan, default is yes, Ex: -sr no
84 | -ss SCAN_SUBDOMAINS, --scan-subdomains SCAN_SUBDOMAINS
85 | Run subdomains scan, default is yes, Ex: -ss no
86 | -sa SCAN_APPS, --scan-apps SCAN_APPS
87 | Run App scan, default is yes, Ex: -sa no
88 | -si SCAN_IPS, --scan-ips SCAN_IPS
89 | Run IPs scan, default is yes, Ex: -si no
90 | -sc SCAN_CLIENTS, --scan-clients SCAN_CLIENTS
91 | Run clients scan, default is yes, Ex: -sc no
92 | -se SCAN_EMPLOYEES, --scan-employees SCAN_EMPLOYEES
93 | Run employees scan, default is yes, Ex: -se no
94 | -o OUTPUT, --output OUTPUT
95 | filename to save the result
96 | -ot {text,json,csv}, --output-type {text,json,csv}
97 | output format default is "text".
98 | -t TIMEOUT, --timeout TIMEOUT
99 | request timeout default is 360 sec.
100 | -l LIMIT, --limit LIMIT
101 | limit results default is 10000.
102 | -nc, --no-color show result without color.
103 | -lv, --list-view-type
104 | list view type.
105 | -lm, --list-methods list methods.
106 | --init INIT set the api token.
107 | -v, --version version.
108 | ```
109 |
110 | Examples:
111 | ```
112 | # Scan company by domain
113 | chiasmodon_cli.py example.com --scan
114 |
115 | # Search for target domain, you will see the result for only this "example.com"
116 | chiasmodon_cli.py example.com
117 |
118 | # Search in target and target subdomains
119 | chiasmodon_cli.py example.com --method domain.all
120 |
121 | # Search for target subdomains
122 | chiasmodon_cli.py example.com --view-type subdomain
123 |
124 | # Search for all creds in United States
125 | chiasmodon_cli.py US --method cred.country
126 |
127 | # Search for related companies by domain
128 | chiasmodon_cli.py example.com --view-type related
129 |
130 | # search for target app id
131 | chiasmodon_cli.py com.discord --method app.id
132 |
133 | # search for target app domain
134 | chiasmodon_cli.py discord.com --method app.domain
135 |
136 | # search for target app name
137 | chiasmodon_cli.py Discord --method app.name
138 |
139 | # Search for ip asn
140 | chiasmodon_cli.py AS123 --method ip.asn
141 |
142 | # Search for cred username
143 | chiasmodon_cli.py someone --method cred.username
144 |
145 | # Search for cred password
146 | chiasmodon_cli.py example@123 --method cred.password
147 |
148 | # Search for url endpoint
149 | chiasmodon_cli.py /wp-login.php --method url.path
150 |
151 | # Search for ip
152 | chiasmodon_cli.py 1.1.1.1 --method ip
153 |
154 | # Search for cidr
155 | chiasmodon_cli.py xx.xx.xx.0/24 --method ip
156 |
157 | # Search for target creds by domain emsils
158 | chiasmodon_cli.py example.com --method cred.email.domain
159 |
160 | # Search for target email
161 | chiasmodon_cli.py someone@example.com --method cred.email
162 |
163 | # search for multiple targets:
164 | chiasmodon_cli.py targets.txt --method domain --output example-creds.txt
165 | ```
166 |
167 | Please note that these examples represent only a fraction of the available options and use cases. Refer to the documentation for more detailed instructions and explore the full range of features provided by Chiasmodon.
168 |
169 |
170 | ## 💬 Contributions and Feedback
171 |
172 | Contributions and feedback are welcome! If you encounter any issues or have suggestions for improvements, please submit them to the Chiasmodon GitHub repository. Your input will help us enhance the tool and make it more effective for the OSINT community.
173 |
174 | ## 📜License
175 |
176 | Chiasmodon is released under the [MIT License](https://opensource.org/licenses/MIT). See the [LICENSE](https://github.com/chiasmodon/LICENSE.txt) file for more details.
177 |
178 | ## ⚠️Disclaimer
179 |
180 | Chiasmodon is intended for legal and authorized use only. Users are responsible for ensuring compliance with applicable laws and regulations when using the tool. The developers of Chiasmodon disclaim any responsibility for the misuse or illegal use of the tool.
181 |
182 | ## 📢Acknowledgments
183 |
184 | Chiasmodon is the result of collaborative efforts from a dedicated team of contributors who believe in the power of OSINT. We would like to express our gratitude to the open-source community for their valuable contributions and support.
185 |
186 | ## 🔗Chiasmodon Links
187 |
188 | - [🐍 Python Library](https://pypi.org/project/chiasmodon)
189 | - [📱 Mobile (APK)](https://github.com/chiasmod0n/chiasmodon-mobile)
190 | - [🌐 Website](http://chiasmodon.online)
191 | - [💬 Telegram](https://t.me/chiasmod0n)
192 | - [🐦 X/Twitter](https://x.com/chiasmod0n)
193 |
194 |
195 | ## ⭐️Star History
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/cli/chiasmodon_cli.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # PYTHON_ARGCOMPLETE_OK
3 |
4 | import os
5 | import sys
6 | import argcomplete
7 | import json
8 | import argparse
9 | import tldextract
10 | from yaspin import yaspin
11 | from pathlib import Path
12 | from pychiasmodon import Chiasmodon,Result,VERSION,VIEW_TYPE_LIST,T,_METHODS
13 |
14 | ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
15 |
16 | class ULIT:
17 | @staticmethod
18 | def rFile(file:Path) -> str:
19 | if not file.is_file():
20 | print(f'{T.RED}Not found {file} file.{T.RESET}')
21 | return
22 |
23 | with open(file, 'r') as f:
24 | return f.read()
25 |
26 | @staticmethod
27 | def wFile(file:Path, data:str) -> str:
28 | with open(file, 'w') as f:
29 | f.write(data)
30 |
31 | @staticmethod
32 | def rFileToJson(file:Path) -> dict:
33 | return json.loads(ULIT.rFile(file=file))
34 |
35 | @staticmethod
36 | def wJsonToFile(file:Path, data:dict) -> dict:
37 | ULIT.wFile(
38 | file=file,
39 | data=json.dumps(data)
40 | )
41 |
42 | @staticmethod
43 | def get_root_domain(d:str) -> str:
44 | domain = d.split()[0]
45 | x = tldextract.extract(domain)
46 | if not x.suffix:
47 | return None
48 |
49 | return '{}.{}'.format(x.domain, x.suffix)
50 |
51 | class Scan(Chiasmodon):
52 | def __init__(self, options:argparse.Namespace) -> None:
53 | self.options :argparse.Namespace=options
54 | self.result :list = []
55 | conf_file :Path = Path(ROOT_DIR, 'conf.json')
56 | token:str = ''
57 |
58 | if not conf_file.is_file():ULIT.wJsonToFile(conf_file, {})
59 |
60 | if self.options.init:
61 | token = self.options.init
62 | ULIT.wJsonToFile(conf_file, {'token':self.options.init})
63 |
64 | elif not conf_file.is_file():
65 | ULIT.wJsonToFile(conf_file, {})
66 | token = ''
67 |
68 | else:
69 | token = ULIT.rFileToJson(conf_file).get('token') or ''
70 |
71 | super().__init__(
72 | token=token,
73 | conf_file=conf_file,
74 | color=True if not self.options.no_color else False,
75 | debug=True,
76 | check_token=self.options.init,
77 | )
78 |
79 | if self.options.init:
80 | sys.exit()
81 |
82 | self.scan_mode = True
83 |
84 | def scan_callback(self,beta,ys):
85 | self.print(beta.print(), ys)
86 |
87 | def proc(self):
88 | if not self.options.query:
89 | self.print(f"{T.RED}You can't run scan without company domain\nPlease use (-d or --domain) to scan the domain{T.RESET}")
90 | sys.exit(0)
91 |
92 | domain = ULIT.get_root_domain(self.options.query)
93 | if not domain:
94 | self.print(f"{T.RED}Wrong domain{T.RESET}",)
95 | sys.exit(0)
96 |
97 | self.output_folder = Path(domain)
98 | self.output_folder.mkdir(exist_ok=True, parents=True)
99 |
100 | self.__scan(
101 | domain=domain,
102 | )
103 |
104 |
105 | def __scan(self, domain):
106 | status = False
107 |
108 | print_output = f'{T.MAGENTA}>{T.RESET}{T.YELLOW} Saved output{T.RESET}: \n'
109 | output = {
110 | 'related':[],
111 | 'apps':[],
112 | 'client-creds':[],
113 | 'client-usernames':[],
114 | 'client-passwords':[],
115 | 'client-emails':[],
116 | 'employe-creds':[],
117 | 'employe-usernames':[],
118 | 'employe-passwords':[],
119 | 'employe-emails':[],
120 | 'subdomains':[],
121 | 'urls':[],
122 | 'endpoints':[],
123 | 'ports':[],
124 |
125 | }
126 |
127 | if self.options.scan_related.lower() == 'yes':
128 | related = self.search(
129 | method='domain',
130 | query=domain,
131 | view_type='related',
132 | sort=True ,
133 | timeout=self.options.timeout,
134 | limit=1000000,
135 | callback_view_result=self.scan_callback,
136 | yaspin=yaspin,
137 | search_text=f'Find {T.GREEN+domain+T.RESET} related companies...',
138 | err_text=f'Not found related !'
139 | )
140 |
141 |
142 | if related:
143 | status = True
144 | output['related'] = [i.save_format() for i in related]
145 | ULIT.wFile((self.output_folder / 'related.txt'), '\n'.join(output['related']))
146 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'related.txt')}{T.RESET}\n"
147 | else:
148 | self.print(f'{T.RED}💥 Not found related !{T.RESET}')
149 | self.print(f'{T.MAGENTA}{"-"*30}{T.RESET}')
150 | if self.options.scan_subdomains.lower() == 'yes':
151 | subdomains = self.search(
152 | method='domain',
153 | query=domain,
154 | view_type='subdomain',
155 | sort=True ,
156 | timeout=self.options.timeout,
157 | limit=1000000,
158 | callback_view_result=self.scan_callback,
159 | yaspin=yaspin,
160 | search_text=f'Find {T.GREEN+domain+T.RESET} subdomains...',
161 | err_text=f'Not found subdomains !'
162 | )
163 |
164 |
165 | if subdomains:
166 | status = True
167 | output['subdomains'] = [i.save_format() for i in subdomains]
168 | ULIT.wFile((self.output_folder / 'subdomains.txt'), '\n'.join(output['subdomains']))
169 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'subdomains.txt')}{T.RESET}\n"
170 | self.print(f'{T.MAGENTA}{"-"*30}{T.RESET}')
171 |
172 | if self.options.scan_apps.lower() == 'yes':
173 | apps = self.search(
174 | method='app.domain',
175 | query=domain,
176 | view_type='app',
177 | sort=True ,
178 | timeout=self.options.timeout,
179 | limit=1000000,
180 | callback_view_result=self.scan_callback,
181 | yaspin=yaspin,
182 | search_text=f'Find {T.GREEN+domain+T.RESET} Apps...',
183 | err_text=f'Not found apps !'
184 | )
185 |
186 |
187 | if apps:
188 | status = True
189 | output['apps'] = [i.save_format() for i in apps]
190 | ULIT.wFile((self.output_folder / 'apps.txt'), '\n'.join(output['apps']))
191 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'apps.txt')}{T.RESET}\n"
192 | self.print(f'{T.MAGENTA}{"-"*30}{T.RESET}')
193 | if self.options.scan_ips.lower() == 'yes':
194 | ips = self.search(
195 | method='domain.all',
196 | query=domain,
197 | view_type='ip',
198 | sort=True ,
199 | timeout=self.options.timeout,
200 | limit=1000000,
201 | callback_view_result=self.scan_callback,
202 | yaspin=yaspin,
203 | search_text=f'Find {T.GREEN+domain+T.RESET} IPs...',
204 | err_text=f'Not found ips !'
205 | )
206 |
207 |
208 | if ips:
209 | status = True
210 | output['ips'] = [i.save_format() for i in ips]
211 | ULIT.wFile((self.output_folder / 'ips.txt'), '\n'.join(output['ips']))
212 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'ips.txt')}{T.RESET}\n"
213 |
214 | self.print(f'{T.MAGENTA}{"-"*30}{T.RESET}')
215 | if self.options.scan_clients.lower() == 'yes':
216 | client_creds:list[Result] = self.search(
217 | query=domain,
218 | method='domain.all',
219 | view_type='full',
220 | sort=True ,
221 | timeout=self.options.timeout,
222 | limit=1000000,
223 | callback_view_result=self.scan_callback,
224 | yaspin=yaspin,
225 | search_text=f'Find {T.GREEN+domain+T.RESET} client creds...',
226 | err_text=f'Not found clients !'
227 |
228 | )
229 |
230 | if client_creds:
231 | status = True
232 | output['client-creds'] =[i.save_format() for i in client_creds]
233 | ULIT.wFile((self.output_folder / 'client-creds.txt'), '\n'.join([':'.join(i) for i in output['client-creds']]))
234 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'client-creds.txt')}{T.RESET}\n"
235 |
236 | for i in client_creds:
237 | output['client-usernames'].append(i.credUsername) if i.credUsername and not i.credEmail and '/' not in i.credUsername and i.credUsername not in output['client-usernames'] else None
238 | output['client-passwords'].append(i.credPassword) if i.credPassword and i.credPassword not in output['client-passwords'] else None
239 | output['client-emails'].append(i.credEmail) if i.credEmail and i.credEmail not in output['client-emails'] else None
240 | output['subdomains'].append(i.domain) if i.domain and i.domain not in output['subdomains'] and i.domain != domain else None
241 | output['urls'].append(i.url) if i.url and i.url not in output['urls'] else None
242 | output['endpoints'].append(i.urlPath) if i.urlPath and i.urlPath not in output['endpoints'] else None
243 | output['ports'].append(i.urlPort) if i.urlPort and i.urlPort not in output['ports'] else None
244 |
245 | ULIT.wFile((self.output_folder / 'client-usernames.txt'), '\n'.join(output['client-usernames']))
246 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'client-usernames.txt')}{T.RESET}\n"
247 | ULIT.wFile((self.output_folder / 'client-emails.txt'), '\n'.join(output['client-emails']))
248 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'client-emails.txt')}{T.RESET}\n"
249 | ULIT.wFile((self.output_folder / 'client-passwords.txt'), '\n'.join(output['client-passwords']))
250 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'client-passwords.txt')}{T.RESET}\n"
251 | ULIT.wFile((self.output_folder / 'endpoints.txt'), '\n'.join(output['endpoints']))
252 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'endpoints.txt')}{T.RESET}\n"
253 | ULIT.wFile((self.output_folder / 'ports.txt'), '\n'.join([f"{i}" for i in output['ports']]))
254 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'ports.txt')}{T.RESET}\n"
255 | ULIT.wFile((self.output_folder / 'subdomains.txt'), '\n'.join(output['subdomains']))
256 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'subdomains.txt')}{T.RESET}\n"
257 | ULIT.wFile((self.output_folder / 'urls.txt'), '\n'.join([f"{i}" for i in output['urls']]))
258 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'urls.txt')}{T.RESET}\n"
259 |
260 |
261 | self.print(f'{T.MAGENTA}{"-"*30}{T.RESET}')
262 | if self.options.scan_employees.lower() == 'yes':
263 | employe_creds = self.search(
264 | query=domain,
265 | method='cred.email.domain',
266 | view_type='full',
267 | sort=True,
268 | timeout=self.options.timeout,
269 | callback_view_result=self.scan_callback,
270 | limit=1000000,
271 | yaspin=yaspin,
272 | search_text=f'Find {T.GREEN+domain+T.RESET} employees creds...',
273 | err_text=f'Not found Employees!'
274 | )
275 |
276 | if employe_creds:
277 | status = True
278 | output['employe-creds'] =[i.save_format() for i in employe_creds]
279 | ULIT.wFile((self.output_folder / 'employe-creds.txt'), '\n'.join([':'.join(i) for i in output['employe-creds']]))
280 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'employe-creds.txt')}{T.RESET}\n"
281 | for i in employe_creds:
282 | output['employe-usernames'].append(i.credUsername) if i.credUsername and not i.credEmail and '/' not in i.credUsername and i.credUsername not in output['employe-usernames'] else None
283 | output['employe-passwords'].append(i.credPassword) if i.credPassword and i.credPassword not in output['employe-passwords'] else None
284 | output['employe-emails'].append(i.credEmail) if i.credEmail and i.credEmail not in output['employe-emails'] else None
285 |
286 | ULIT.wFile((self.output_folder / 'employe-usernames.txt'), '\n'.join(output['employe-usernames']))
287 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'employe-usernames.txt')}{T.RESET}\n"
288 | ULIT.wFile((self.output_folder / 'employe-emails.txt'), '\n'.join(output['employe-emails']))
289 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'employe-emails.txt')}{T.RESET}\n"
290 | ULIT.wFile((self.output_folder / 'employe-passwords.txt'), '\n'.join(output['employe-passwords']))
291 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'employe-passwords.txt')}{T.RESET}\n"
292 |
293 |
294 | if status:
295 | ULIT.wJsonToFile((self.output_folder / 'scan.json'), output)
296 | print_output += f"\t{T.MAGENTA}-{T.RESET} {T.BLUE}{(self.output_folder / 'scan.json')}{T.RESET}"
297 | self.print(print_output)
298 |
299 | class CLI(Chiasmodon):
300 | def __init__(self, options:argparse.Namespace) -> None:
301 |
302 | self.options :argparse.Namespace=options
303 | self.result :list = []
304 |
305 | conf_file :Path = Path(ROOT_DIR, 'conf.json')
306 | token:str = ''
307 |
308 | if not conf_file.is_file():ULIT.wJsonToFile(conf_file, {})
309 |
310 | if self.options.init:
311 | token = self.options.init
312 | ULIT.wJsonToFile(conf_file, {'token':self.options.init})
313 |
314 | elif not conf_file.is_file():
315 | ULIT.wJsonToFile(conf_file, {})
316 | token = ''
317 |
318 | else:
319 | token = ULIT.rFileToJson(conf_file).get('token') or ''
320 |
321 | super().__init__(
322 | token=token,
323 | conf_file=conf_file,
324 | color=True if not self.options.no_color else False,
325 | #debug=self.options.debug,
326 | debug=True,
327 | check_token=self.options.init,
328 | )
329 |
330 | if self.options.init:
331 | sys.exit()
332 |
333 | def review_results(self,
334 | beta:Result,
335 | ys=True,
336 | ) -> None:
337 |
338 | if beta.save_format() not in self.result:
339 | self.print(beta.print(), ys=ys)
340 | self.result.append(beta.save_format())
341 |
342 | def save_result(self, view_type) -> None:
343 |
344 | if self.options.output:
345 |
346 | if self.options.output_type == "text":
347 | if self.result and view_type != 'cred':
348 | self.result.remove(None) if None in self.result else None
349 |
350 | ULIT.wFile(
351 | self.options.output,
352 | '\n'.join([':'.join(i) if type(i) == list else i for i in self.result])
353 | )
354 |
355 | if self.options.output_type == "csv":
356 | if self.result and view_type != 'cred':
357 | self.result.remove(None) if None in self.result else None
358 |
359 | ULIT.wFile(
360 | self.options.output,
361 | '\n'.join([','.join(['url/app_id','user/email', 'password', 'country', 'date'])]+[','.join(i) if type(i) == list else i for i in self.result]) if view_type == 'cred' else '\n'.join([view_type]+[','.join(i) if type(i) == list else i for i in self.result])
362 | )
363 |
364 | if self.options.output_type == "json":
365 | ULIT.wJsonToFile(
366 | self.options.output,
367 | self.result
368 | )
369 |
370 |
371 | def proc(self):
372 |
373 | query = ULIT.rFile(f).splitlines() if (f:=Path(self.options.query)).is_file() else [self.options.query.strip()]
374 |
375 |
376 | for i in query:
377 | self.search(
378 | query=i,
379 | method=self.options.method,
380 | view_type=self.options.view_type,
381 | limit=self.options.limit,
382 | timeout=self.options.timeout,
383 | sort=True,
384 | yaspin=yaspin,
385 | callback_view_result=self.review_results,
386 | )
387 |
388 | if self.options.output and self.result:
389 | self.save_result(self.options.view_type)
390 | self.print(f'{T.MAGENTA}>{T.RESET}{T.YELLOW} Saved output to {T.RESET}: {T.GREEN}{self.options.output}{T.RESET}')
391 |
392 |
393 | if __name__ == "__main__":
394 |
395 | if len(sys.argv) == 1 or '--help' in sys.argv or '-h' in sys.argv:
396 | print(f"""
397 | 🙂 🙂
398 | /|\\ /|\\
399 | /\\ /\\
400 | \\___/ \\___/ 🔑
401 | {T.BLUE}~^~^~^~^~^~^~^~^~^~^~^~^~{T.BLUE}~^~^~^~{T.GREEN} {T.RESET}/|\\{T.GREEN}
402 | |\\ {T.YELLOW}\\\\\\\\{T.GREEN}__ {T.MAGENTA}Chiasmodon{T.RESET} {T.RED}{VERSION}{T.GREEN} {T.RESET}/\\{T.GREEN}
403 | | \\_/ {T.RED}o{T.GREEN} \\ {T.CYAN}o{T.GREEN} {T.RESET}\\___/{T.GREEN}
404 | > _ {T.YELLOW}(({T.GREEN} <_ {T.CYAN}oo{T.GREEN}
405 | | / \\__+___/
406 | |/ |/
407 |
408 | {T.MAGENTA}>{T.RESET} {T.YELLOW}Admin{T.RESET}: {T.GREEN}https://t.me/Chiasmod0n
409 | {T.RESET}""")
410 | parser = argparse.ArgumentParser(description='Chiasmodon CLI', formatter_class=argparse.RawTextHelpFormatter,)
411 |
412 | if '-lm' not in sys.argv and '--list-methods' not in sys.argv and '-lv' not in sys.argv and '--list-view-type' not in sys.argv and '-v' not in sys.argv and '--version' not in sys.argv and '--init' not in sys.argv:
413 | parser.add_argument('query', type=str, help='query argument')
414 |
415 | parser.add_argument('-m','--method', help='method to search by it,default is "domain".', choices=_METHODS, type=str, default='domain')
416 | parser.add_argument('-vt','--view-type', help='type view the result default is "full".', choices=VIEW_TYPE_LIST, type=str, default='full')
417 | parser.add_argument('-s','--scan', help='scan the company domain (Related company, Clients, Employees, Company ASNs, Company Apps).',action='store_true')
418 | parser.add_argument('-sr','--scan-related', help='Run related scan, default is yes, Ex: -sr no',type=str, default='yes')
419 | parser.add_argument('-ss','--scan-subdomains', help='Run subdomains scan, default is yes, Ex: -ss no',type=str, default='yes')
420 | parser.add_argument('-sa','--scan-apps', help='Run App scan, default is yes, Ex: -sa no',type=str, default='yes')
421 | parser.add_argument('-si','--scan-ips', help='Run IPs scan, default is yes, Ex: -si no',type=str, default='yes')
422 | parser.add_argument('-sc','--scan-clients', help='Run clients scan, default is yes, Ex: -sc no',type=str, default='yes')
423 | parser.add_argument('-se','--scan-employees',help='Run employees scan, default is yes, Ex: -se no',type=str, default='yes')
424 | parser.add_argument('-o','--output', help='filename to save the result', type=str,)
425 | parser.add_argument('-ot','--output-type', help='output format default is "text".', choices=['text', 'json', 'csv'], type=str, default='text')
426 | parser.add_argument('-t','--timeout', help='request timeout default is 360 sec.',type=int, default=360)
427 | parser.add_argument('-l','--limit', help='limit results default is 10000.',type=int, default=10000)
428 | parser.add_argument('-nc','--no-color', help='show result without color.',action='store_true')
429 | parser.add_argument('-lv','--list-view-type',help='list view type.',action='store_true')
430 | parser.add_argument('-lm','--list-methods', help='list methods.', action='store_true')
431 | parser.add_argument('--init', help='set the api token.',type=str)
432 |
433 | parser.add_argument('-v','--version', help='version.',action='store_true')
434 |
435 | parser.epilog = f'''
436 | Examples:
437 |
438 | # Scan company by domain
439 | {Path(sys.argv[0]).name} example.com --scan
440 |
441 | # Search for target domain, you will see the result for only this "example.com"
442 | {Path(sys.argv[0]).name} example.com
443 |
444 | # Search in target and target subdomains
445 | {Path(sys.argv[0]).name} example.com --method domain.all
446 |
447 | # Search for target subdomains
448 | {Path(sys.argv[0]).name} example.com --view-type subdomain
449 |
450 | # Search for all creds in United States
451 | {Path(sys.argv[0]).name} US --method cred.country
452 |
453 | # Search for related companies by domain
454 | {Path(sys.argv[0]).name} example.com --view-type related
455 |
456 | # search for target app id
457 | {Path(sys.argv[0]).name} com.discord --method app.id
458 |
459 | # search for target app domain
460 | {Path(sys.argv[0]).name} discord.com --method app.domain
461 |
462 | # search for target app name
463 | {Path(sys.argv[0]).name} Discord --method app.name
464 |
465 | # Search for ip asn
466 | {Path(sys.argv[0]).name} AS123 --method ip.asn
467 |
468 | # Search for cred username
469 | {Path(sys.argv[0]).name} someone --method cred.username
470 |
471 | # Search for cred password
472 | {Path(sys.argv[0]).name} example@123 --method cred.password
473 |
474 | # Search for url endpoint
475 | {Path(sys.argv[0]).name} /wp-login.php --method url.path
476 |
477 | # Search for ip
478 | {Path(sys.argv[0]).name} 1.1.1.1 --method ip
479 |
480 | # Search for cidr
481 | {Path(sys.argv[0]).name} xx.xx.xx.0/24 --method ip
482 |
483 | # Search for target creds by domain emsils
484 | {Path(sys.argv[0]).name} example.com --method cred.email.domain
485 |
486 | # Search for target email
487 | {Path(sys.argv[0]).name} someone@example.com --method cred.email
488 |
489 | # search for multiple targets:
490 | {Path(sys.argv[0]).name} targets.txt --method domain --output example-creds.txt
491 | '''
492 |
493 | argcomplete.autocomplete(parser)
494 | args = parser.parse_args()
495 | if args.list_view_type:
496 | for i in VIEW_TYPE_LIST:
497 | print(i)
498 | sys.exit(0)
499 |
500 | if args.list_methods:
501 | for i in _METHODS:
502 | print(i)
503 | sys.exit(0)
504 |
505 | if args.version:
506 | print(VERSION)
507 | sys.exit(0)
508 |
509 | if args.scan:
510 | root=Scan(options=args)
511 | root.proc()
512 |
513 | else:
514 | root=CLI(options=args)
515 | root.proc()
516 |
--------------------------------------------------------------------------------
/pychiasmodon.py:
--------------------------------------------------------------------------------
1 | import re
2 | import os
3 | import sys
4 | import time
5 | import requests
6 | from yaspin import Spinner
7 |
8 | VERSION = "3.0.2"
9 | _API_URL = 'http://chiasmodon.online/v2/api/beta'
10 | _API_HEADERS = {'user-agent':'cli/python'}
11 | _VIEW_TYPE = {
12 | 'full':[
13 | 'cred.username',
14 | 'cred.phone',
15 | 'cred.password',
16 | 'cred.email',
17 | 'cred.email.domain',
18 | 'cred.country',
19 | 'domain',
20 | 'domain.all',
21 |
22 | 'ip',
23 | 'ip.asn',
24 | 'ip.isp',
25 | 'ip.org',
26 | 'ip.port',
27 | 'ip.country',
28 | 'app.id',
29 | 'app.name',
30 | 'app.domain',
31 | 'url.path',
32 | 'url.port',
33 | ],
34 | 'cred':[
35 | 'cred.phone',
36 | 'cred.username',
37 | 'cred.password',
38 | 'cred.email',
39 | 'cred.email.domain',
40 | 'cred.country',
41 | 'domain',
42 | 'domain.all',
43 |
44 | 'ip',
45 | 'ip.asn',
46 | 'ip.isp',
47 | 'ip.org',
48 | 'ip.port',
49 | 'ip.country',
50 | 'app.id',
51 | 'app.name',
52 | 'app.domain',
53 | 'url.path',
54 | 'url.port',
55 | ],
56 | 'url':[
57 | 'cred.username',
58 | 'cred.password',
59 | 'cred.phone',
60 | 'cred.email',
61 | 'cred.email.domain',
62 | 'cred.country',
63 | 'domain',
64 | 'domain.all',
65 | 'ip',
66 | 'ip.asn',
67 | 'ip.isp',
68 | 'ip.org',
69 | 'ip.port',
70 | 'ip.country',
71 | 'url.path',
72 | 'url.port',
73 | ],
74 | 'email':[
75 | 'cred.username',
76 | 'cred.phone',
77 | 'cred.password',
78 | 'cred.country',
79 | 'cred.email.domain',
80 | 'domain',
81 | 'domain.all',
82 | 'ip',
83 | 'ip.asn',
84 | 'ip.isp',
85 | 'ip.org',
86 | 'ip.port',
87 | 'ip.country',
88 | 'app.id',
89 | 'app.name',
90 | 'app.domain',
91 | 'url.path',
92 | 'url.port',
93 | ],
94 | 'phone':[
95 | 'cred.username',
96 | 'cred.email',
97 | 'cred.email.domain',
98 | 'domain',
99 | 'domain.all',
100 | 'ip',
101 | 'ip.asn',
102 | 'ip.isp',
103 | 'ip.org',
104 | 'ip.port',
105 | 'ip.country',
106 | 'app.id',
107 | 'app.name',
108 | 'app.domain',
109 | 'url.path',
110 | 'url.port',
111 | 'cred.country',
112 | ],
113 | 'password':[
114 | 'cred.username',
115 | 'cred.phone',
116 |
117 | 'cred.email',
118 | 'cred.email.domain',
119 | 'domain',
120 | 'domain.all',
121 | 'ip',
122 | 'ip.asn',
123 | 'ip.isp',
124 | 'ip.org',
125 | 'ip.port',
126 | 'ip.country',
127 | 'app.id',
128 | 'app.name',
129 | 'app.domain',
130 | 'url.path',
131 | 'url.port',
132 | 'cred.country',
133 | ],
134 | 'username': [
135 | 'cred.phone',
136 | 'cred.password',
137 | 'domain',
138 | 'domain.all',
139 | 'ip',
140 | 'ip.asn',
141 | 'ip.isp',
142 | 'ip.org',
143 | 'ip.port',
144 | 'ip.country',
145 | 'app.id',
146 | 'app.name',
147 | 'app.domain',
148 | 'url.path',
149 | 'url.port',
150 | 'cred.country',
151 | ],
152 | 'app':[
153 | 'cred.phone',
154 | 'cred.username',
155 | 'cred.password',
156 | 'cred.email',
157 | 'cred.email.domain',
158 | 'cred.country',
159 | 'app.domain'
160 | ],
161 | 'domain':[
162 | 'cred.username',
163 | 'cred.phone',
164 | 'cred.password',
165 | 'cred.email',
166 | 'cred.email.domain',
167 | 'cred.country',
168 | 'domain',
169 | 'domain.all',
170 |
171 | 'ip',
172 | 'ip.asn',
173 | 'ip.isp',
174 | 'ip.org',
175 | 'ip.port',
176 | 'ip.country',
177 | 'app.id',
178 | 'app.name',
179 | 'app.domain',
180 | 'url.path',
181 | 'url.port',
182 | ],
183 | 'ip':[
184 | 'cred.username',
185 | 'cred.phone',
186 | 'cred.password',
187 | 'cred.email',
188 | 'cred.email.domain',
189 | 'domain',
190 | 'domain.all',
191 | 'ip.asn',
192 | 'ip.isp',
193 | 'ip.org',
194 | 'ip.port',
195 | 'ip.country',
196 | 'app.id',
197 | 'app.name',
198 | 'app.domain',
199 | 'url.path',
200 | 'url.port',
201 | 'cred.country',
202 | ],
203 | 'related':[
204 | 'domain',
205 | ],
206 | 'subdomain':[
207 | 'domain'
208 | ]
209 | }
210 |
211 | _METHODS = [
212 | # cred
213 | 'cred.username', # Query like -> somone
214 | 'cred.password', # Query like -> lol@123
215 | 'cred.email', # Query like -> somone@example.com
216 | 'cred.phone', # Query line -> xxxxxxxx # without : + or - or space or ) or (
217 | 'cred.email.domain', # Query like -> example.com
218 | 'cred.country', # Query like -> US
219 |
220 | # domain
221 | 'domain', # Query like -> example.com
222 | 'domain.all', # Query like -> example.com
223 |
224 | # ip
225 | 'ip', # Query like -> 1.1.1.1
226 | 'ip.asn', # Query like -> as123
227 | 'ip.isp', # Query like -> "isp company"
228 | 'ip.org', # Query like -> "org name"
229 | 'ip.port', # Query like -> 22
230 | 'ip.country', # Query like -> US
231 |
232 | # app
233 | 'app.id', # Query like -> com.example
234 | 'app.name', # Query like -> Example
235 | 'app.domain', # Query like -> example.com
236 |
237 | # url
238 | 'url.path', # Query like -> "isp company"
239 | 'url.port', # Query like -> 8080
240 | ]
241 |
242 | VIEW_TYPE_LIST = list(_VIEW_TYPE.keys())
243 |
244 | class T:
245 | RED = '\033[91m'
246 | GREEN = '\033[92m'
247 | YELLOW = '\033[93m'
248 | BLUE = '\033[94m'
249 | MAGENTA = '\033[95m'
250 | CYAN = '\033[96m'
251 | RESET = '\033[0m'
252 |
253 |
254 | class Chiasmodon:
255 | def __init__(self, token=None, color=True, debug=True,conf_file=None,check_token=True) -> None:
256 | self.token = token
257 | self.conf_file = conf_file
258 | self.debug = debug
259 | self.err :bool = False
260 | self.msg :str = ''
261 | self.__result:list[Result] = []
262 | self.scan_mode = False
263 |
264 | if not color:
265 | T.RED = ''
266 | T.GREEN = ''
267 | T.YELLOW = ''
268 | T.BLUE = ''
269 | T.MAGENTA = ''
270 | T.CYAN = ''
271 | T.RESET = ''
272 |
273 | if self.token and check_token:
274 | if self.__check_token():
275 | self.print(f'{T.GREEN}Set token successfully{T.RESET}')
276 |
277 | else:
278 | try:os.remove(conf_file)
279 | except:pass
280 |
281 | self.print(f'{T.RED}{self.msg}{T.RESET}')
282 | return
283 |
284 | def proc_all_domains(self,
285 | query,
286 | view_type,
287 | sort,
288 | timeout,
289 | limit,
290 | callback_view_result,
291 | yaspin,
292 | search_text,
293 | err_text) -> list:
294 |
295 |
296 | domains :list = self.__proc_query(
297 | query=query,
298 | method='domain',
299 | view_type='subdomain',
300 | sort=sort,
301 | timeout=timeout,
302 | limit=limit,
303 | callback_view_result=None,
304 | yaspin=None,
305 | search_text=search_text,
306 | err_text=err_text,
307 | )
308 | self.__result :list = []
309 | result :list = []
310 |
311 | domains = [i.domain for i in domains]
312 | if query not in domains:domains.append(query)
313 |
314 | for domain in domains:
315 | result.extend(self.__proc_query(
316 | query=domain,
317 | method='domain',
318 | view_type=view_type,
319 | sort=sort,
320 | timeout=timeout,
321 | limit=limit,
322 | callback_view_result=callback_view_result,
323 | yaspin=yaspin,
324 | search_text=search_text.replace(query, domain),
325 | err_text=err_text,
326 | ))
327 | self.__result :list = []
328 |
329 | return result
330 |
331 |
332 | def filter(self,query:str,method:str):
333 |
334 | if 'domain' in method:
335 | if not re.match(r"^(?!.*\d+\.\d+\.\d+\.\d+$)[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", query):
336 | self.print(f'{T.RED}Your format query is wrong!\nThis is not domain.{T.RESET}')
337 | return False
338 |
339 | elif method == 'ip':
340 | if not re.match(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", query):
341 | self.print(f'{T.RED}Your format query is wrong!\nAccept only ipv4.{T.RESET}')
342 | return False
343 |
344 | elif method == 'ip.asn':
345 | if not query.lower().startswith('as'):
346 | self.print(f'{T.RED}Your format query is wrong!\nThe ASN starts with AS\nLike this: AS1234.{T.RESET}')
347 | return False
348 |
349 | elif method in ['ip.port', 'url.port']:
350 | if not re.match(r":(\d+)", query):
351 | self.print(f'{T.RED}Your format query is wrong!\nThis is not port.{T.RESET}')
352 | return False
353 |
354 | elif method == 'cred.email':
355 | if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', query):
356 | self.print(f'{T.RED}Your format query is wrong!\nThis is not email.{T.RESET}')
357 | return False
358 |
359 | elif method == 'cred.country' or method == 'ip.country':
360 | if not len(query) == 2:
361 | self.print(f'{T.RED}Your format query is wrong!\nAccept only country code.{T.RESET}')
362 | return False
363 |
364 | elif method == 'url.path':
365 | if not query[0] == '/':
366 | self.print(f'{T.RED}Your format query is wrong!\nThe url path moset be like: /somthing{T.RESET}')
367 | return False
368 |
369 | return query
370 |
371 | def print(self,text, ys=None, ys_err=False) -> None:
372 | if text == None:return
373 | if self.debug:
374 | if ys:
375 | if not ys_err:
376 | ys.write(text)
377 | else:
378 | ys.text = text
379 | else:
380 | print(text)
381 |
382 | def __check_token(self):
383 | if self.__request({
384 | 'token' : self.token,
385 | 'version' : VERSION,
386 | 'method' : 'token'
387 | }).get('is_active'):
388 | return True
389 |
390 | return False
391 |
392 | def __request(self, data:dict,timeout=60):
393 |
394 | try:
395 | resp = requests.post(_API_URL, data=data, headers=_API_HEADERS, timeout=timeout)
396 | resp.close()
397 | resp = resp.json()
398 |
399 | try:
400 | if resp.get('err'):
401 | self.err = True
402 | self.msg = resp['msg']
403 | except:pass
404 |
405 | return resp
406 |
407 | except requests.exceptions.ReadTimeout:
408 | self.print(f"{T.RED}\nError: timeout !\nPlease try agine later.{T.RESET}")
409 | sys.exit()
410 |
411 | except requests.exceptions.InvalidJSONError:
412 | self.print(f"{T.RED}\nError: Server send wrong data.\nPlease try agine later.{T.RESET}")
413 | sys.exit()
414 |
415 | except Exception as e:
416 | self.print(f"{T.RED}\nRequest error: {e}\nPlease try agine later.{T.RESET}")
417 | sys.exit()
418 |
419 |
420 | def __proc_query(self,
421 | method:str,
422 | query:str,
423 | view_type:str,
424 | timeout:int,
425 | sort:bool,
426 | limit:int,
427 | yaspin:bool,
428 | callback_view_result,
429 | search_text='',
430 | err_text=''
431 | ) -> dict:
432 | Result.VIEW_TYPE = view_type
433 |
434 | result : list[Result] = []
435 |
436 | data = {
437 |
438 | 'token' : self.token,
439 | 'type-view' : view_type,
440 | 'method' : method,
441 | 'version' : VERSION,
442 | 'query' : query,
443 | 'get-info' : 'yes'
444 | }
445 |
446 | if yaspin:
447 | with yaspin(Spinner(["🐟","🐠","🐡","🐬","🐋","🐳","🦈","🐙","🐚","🪼","🪸"], 200),text=f"Processing {query} ..." if not search_text else search_text) as sp:
448 | process_info = self.__request(
449 | data=data,
450 | timeout=timeout,
451 | )
452 |
453 | if process_info and process_info.get('count') == 0:
454 | if not err_text:
455 | self.print(f"{T.RED}Not found result{T.RESET}", sp,ys_err=True)
456 | else:
457 | self.print(f"{T.RED}{err_text}{T.RESET}", sp,ys_err=True)
458 |
459 |
460 | sp.fail("💥 ")
461 | sp.stop()
462 | return result
463 |
464 | else:
465 | sp.ok("⚓ ")
466 |
467 | else:
468 | process_info = self.__request(
469 | data=data,
470 | timeout=timeout,
471 | )
472 | if process_info and process_info.get('count') == 0:
473 | self.print(f"{T.RED}Not found result{T.RESET}")
474 | return result
475 |
476 |
477 | del data['get-info']
478 |
479 | if self.err:
480 | self.err= False
481 | self.print(f'{T.RED}Error: {self.msg}{T.RESET}',ys_err=True)
482 | return
483 |
484 | if yaspin:
485 | self.print(f"{T.YELLOW}Pages count{T.YELLOW}: {T.GREEN}{process_info['pages'] if process_info['count'] != -1 else 'unknown'}{T.RESET}")
486 |
487 | data['sid'] = process_info['sid']
488 |
489 | if yaspin:
490 | YS = yaspin(f'Get pages 0/{process_info["pages"]}').green.bold.shark #.on_black
491 | YS.start()
492 |
493 | else:
494 | YS = None
495 |
496 | for p in range(1, process_info['pages']+0x1):
497 | if yaspin:YS.text = f'Get pages {p}/{process_info["pages"]}'
498 |
499 | data['page'] = p
500 |
501 | beta_result = self.__request(
502 | data=data,
503 | timeout=timeout,
504 | )
505 |
506 | if self.err:
507 | self.err=False
508 | if yaspin:self.print(f"{T.RED}{self.msg}{T.RESET}", YS, ys_err=True);YS.fail("💥 ");YS.stop()
509 | return result
510 |
511 | for r in beta_result['data']:
512 |
513 | column :Result = Result(**r)
514 |
515 | if sort and column in self.__result:
516 | continue
517 |
518 | if callback_view_result != None:
519 | callback_view_result(beta=column, ys=YS)
520 |
521 | result.append(column)
522 | self.__result.append(column)
523 |
524 | if len(result) == limit:
525 | if yaspin:YS.text='';YS.stop()
526 | return result
527 |
528 | if beta_result['done']:
529 | if yaspin:YS.text='';YS.stop()
530 | return result
531 |
532 | time.sleep(0x1)
533 |
534 | if not result:
535 | if yaspin:self.print(f"{T.RED}Not found result{T.RESET}", YS,ys_err=True);YS.fail("💥 ");YS.stop()
536 | else:self.print(f"{T.RED}Not found result{T.RESET}")
537 | else:
538 | if yaspin:YS.text='';YS.stop()
539 | return result
540 |
541 | def search(self,
542 | query,
543 | method='domain',
544 | view_type='full',
545 | limit=10000,
546 | timeout=60,
547 | sort=True,
548 | yaspin=False,
549 | search_text='',
550 | err_text='',
551 | callback_view_result=None) -> dict:
552 |
553 |
554 | if method not in _METHODS:
555 | raise Exception(f"{T.RED}not found this method: {method}.{T.RESET}")
556 |
557 | if method not in _VIEW_TYPE[view_type]:
558 | raise Exception(f"{T.RED}{view_type} doesn't support ({method}).{T.RESET}")
559 |
560 |
561 | self.err = False
562 | self.msg = ''
563 | result = None
564 |
565 | query = self.filter(query, method)
566 | if query == False:
567 | return
568 |
569 | if method == "domain.all":
570 | result = self.proc_all_domains(
571 | query=query,
572 | view_type=view_type,
573 | sort=sort,
574 | timeout=timeout,
575 | limit=limit,
576 | callback_view_result=callback_view_result,
577 | yaspin=yaspin,
578 | search_text=search_text,
579 | err_text=err_text,
580 | )
581 |
582 | else:
583 |
584 | result = self.__proc_query(
585 | query=query,
586 | method=method,
587 | view_type=view_type,
588 | sort=sort,
589 | timeout=timeout,
590 | limit=limit,
591 | callback_view_result=callback_view_result,
592 | yaspin=yaspin,
593 | search_text=search_text,
594 | err_text=err_text,
595 | )
596 |
597 | self.__result:list = []
598 |
599 | return result
600 |
601 | class Result(dict):
602 | VIEW_TYPE = None
603 | HID_PASS = True if os.environ.get('HID_PASS') else False
604 | def __init__(self,type,**kwargs) -> None:
605 |
606 | self.kwargs = kwargs
607 | Type = type
608 |
609 | self.url = None
610 | self.urlPort = None
611 | self.urlPath = None
612 | self.credEmail = None
613 | self.credUsername = None
614 | self.credPassword = None
615 | self.credCountry = None
616 | self.credDate = None
617 | self.credPhone = None
618 | self.domain = None
619 | self.ip = None
620 | self.ipAsn = None
621 | self.ipIsp = None
622 | self.ipOrg = None
623 | self.ipPorts = None
624 | self.ipCountry = None
625 | self.appID = None
626 | self.appName = None
627 | self.appIcon = None
628 | self.appDomain = None
629 |
630 | if Type == "login":
631 | if kwargs.get('url'):
632 | self.urlPath = kwargs['url']['path']
633 | self.urlPort = kwargs['url']['port']
634 | self.url = self.__convert_url(kwargs['url'])
635 |
636 | if kwargs['url']['ip']:
637 | self.__convert_and_set_ip(kwargs['url']['ip'])
638 |
639 | elif kwargs['url']['domain']:
640 | self.domain = self.__convert_domain(kwargs['url']['domain'])
641 |
642 |
643 | if kwargs.get('app'):
644 | self.appID = kwargs['app']['id']
645 | self.appName = kwargs['app']['name']
646 | self.appIcon = kwargs['app']['icon']
647 | if kwargs['app']['domain']:
648 | self.appDomain = self.__convert_domain(kwargs['app']['domain'])
649 |
650 | if kwargs.get('cred'):
651 | if kwargs['cred']['email']:
652 | self.credEmail = self.__convert_email(kwargs['cred']['email'])
653 |
654 | self.credUsername = kwargs['cred']['username']
655 | self.credPassword = kwargs['cred']['password'] if not self.HID_PASS else '*'*len(kwargs['cred']['password'])
656 | if kwargs['cred']['phone']:
657 | self.credPhone = self.__convert_phone(kwargs['cred']['phone'])
658 |
659 |
660 | if kwargs.get('country'):
661 | self.credCountry = kwargs['country']['f']
662 |
663 | self.credDate = kwargs['date']
664 |
665 | elif Type == 'url':
666 | self.urlPath = kwargs['path']
667 | self.urlPort = kwargs['port']
668 | self.url = self.__convert_url(kwargs)
669 |
670 | if kwargs['ip']:
671 | self.url = self.__convert_url(kwargs)
672 | self.__convert_and_set_ip(kwargs['ip'])
673 |
674 | elif kwargs['domain']:
675 | self.domain = self.__convert_domain(kwargs['domain'])
676 |
677 | elif Type == "email":
678 | self.credEmail = self.__convert_email(kwargs)
679 |
680 | elif Type == "domain":
681 | self.domain = self.__convert_domain(kwargs)
682 |
683 | elif Type == 'app':
684 | self.appID = kwargs['id']
685 | self.appName = kwargs['name']
686 | self.appIcon = kwargs['icon']
687 |
688 | if kwargs['domain']:
689 | self.domain = self.__convert_domain(kwargs['domain'])
690 |
691 |
692 | elif Type == 'ip':
693 | self.__convert_and_set_ip(kwargs)
694 |
695 | def __convert_phone(self,phone):
696 | return f"+{phone['country']['p']} {phone['number']}"
697 |
698 | def __convert_email(self,email):
699 | return f"{email['name']}@{self.__convert_domain(email['domain'])}"
700 |
701 | def __convert_and_set_ip(self,ip):
702 | self.ip = ip['ip']
703 | self.ipAsn = ip['asn']
704 | self.ipOrg = ip['org']
705 | self.ipIsp = ip['isp']
706 | self.ipPorts = ip['ports']
707 | self.ipCountry = ip['country']['f'] if ip['country'] else None
708 |
709 | def __convert_url(self,url:dict):
710 | if url['domain']:
711 | return f"{url['proto']}://{self.__convert_domain(url['domain'])}:{url['port']}{url['path']}"
712 | elif url['ip']:
713 | return f"{url['proto']}://{url['ip']['ip']}:{url['port']}{url['path']}"
714 |
715 |
716 | return None
717 |
718 | def __convert_domain(self,domain:dict):
719 | return f"{(domain['sub']+'.') if domain['sub'] else ''}{domain['name']}{('.'+domain['suffix']) if domain['suffix'] else ''}"
720 |
721 |
722 | def __str__(self) -> str:
723 | return self.save_format()
724 |
725 | def __radd__(self, other):
726 | if isinstance(other, str):
727 | return other + self.save_format()
728 | else:
729 | return NotImplemented
730 |
731 | def __add__(self, other):
732 | if isinstance(other, str):
733 | return self.save_format() + other
734 | else:
735 | return NotImplemented
736 |
737 | def __getattr__(self, key):
738 | if key in self:
739 | return self[key]
740 | else:
741 | raise AttributeError(f"'Result' object has no attribute '{key}'")
742 |
743 | def __setattr__(self, key, value):
744 | self[key] = value
745 |
746 |
747 | def print(self,):
748 | c=""
749 |
750 | if self.VIEW_TYPE == "email" and self.credEmail:
751 | c+=f"{T.MAGENTA}[ {T.YELLOW}Email{T.MAGENTA} ]{T.MAGENTA}> {T.CYAN}{self.credEmail}{T.RESET}"
752 |
753 | if self.VIEW_TYPE == "password" and self.credPassword:
754 | c+=f"{T.MAGENTA}[ {T.YELLOW}Email{T.MAGENTA} ]{T.MAGENTA}> {T.CYAN}{self.credPassword}{T.RESET}"
755 |
756 | if self.VIEW_TYPE == "username" and self.credUsername:
757 | c+=f"{T.MAGENTA}[ {T.YELLOW}Email{T.MAGENTA} ]{T.MAGENTA}> {T.CYAN}{self.credUsername}{T.RESET}"
758 |
759 | if self.VIEW_TYPE == "app" and self.appID:
760 | if self.appID:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.CYAN}{self.appID}{T.RESET}\n"
761 | if self.appName:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Name{T.RESET}{' ':10}: {T.CYAN}{self.appName}{T.RESET}\n"
762 | if self.appIcon:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Icon{T.RESET}{' ':10}: {T.CYAN}{self.appIcon}{T.RESET}\n"
763 | if self.appDomain:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Domain{T.RESET}{' ':8}: {T.CYAN}{self.appDomain}{T.RESET}\n"
764 |
765 | if self.VIEW_TYPE == "url" and self.url:
766 | if self.url:c+=f"{T.MAGENTA}[ {T.YELLOW}URL{T.MAGENTA} ]{T.MAGENTA}> {T.CYAN}{self.url}{T.RESET}\n"
767 | if self.urlPath:c+=f"{T.MAGENTA}[ {T.YELLOW}URL{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Path{T.RESET}{' ':10}: {T.CYAN}{self.urlPath}{T.RESET}\n"
768 | if self.urlPort:c+=f"{T.MAGENTA}[ {T.YELLOW}URL{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Port{T.RESET}{' ':10}: {T.CYAN}{self.urlPort}{T.RESET}\n"
769 |
770 | if self.VIEW_TYPE == "ip" and self.ip:
771 | if self.ip:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.MAGENTA}> {T.BLUE}{self.ip}{T.RESET}\n"
772 | if self.ipPorts:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Ports{T.RESET}{' ':9}: {T.CYAN}{self.ipPorts}{T.RESET}\n"
773 | if self.ipAsn:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Asn{T.RESET}{' ':11}: {T.CYAN}{self.ipAsn}{T.RESET}\n"
774 | if self.ipIsp:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Isp{T.RESET}{' ':11}: {T.CYAN}{self.ipIsp}{T.RESET}\n"
775 | if self.ipOrg:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Org{T.RESET}{' ':11}: {T.CYAN}{self.ipOrg}{T.RESET}\n"
776 | if self.ipCountry:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.MAGENTA}>{T.RED} Country{T.RESET}{' ':7}: {T.CYAN}{self.ipCountry}{T.RESET}\n"
777 |
778 | if self.VIEW_TYPE in ["domain", 'subdomain', 'related'] and self.domain:
779 | c+=f"{T.MAGENTA}[ {T.YELLOW}Domain{T.MAGENTA} ]{T.MAGENTA}> {T.CYAN}{self.domain}{T.RESET}"
780 |
781 | if self.VIEW_TYPE == "phone" and self.credPhone:
782 | return f"{T.MAGENTA}> {T.CYAN}{self.credPhone}{T.RESET}"
783 |
784 | if self.VIEW_TYPE == "cred":
785 | if self.url:c+=f"{T.MAGENTA}[ {T.YELLOW}URL{T.MAGENTA} ]{T.MAGENTA}> {T.BLUE}{self.url}{T.RESET}\n"
786 | if self.appID:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.CYAN}{self.appID}{T.RESET}\n"
787 | if self.credEmail:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Email{T.RESET}{' ':9}: {T.GREEN}{self.credEmail}{T.RESET}\n"
788 | if self.credUsername and not self.credEmail:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Username{T.RESET}{' ':6}: {T.GREEN}{self.credUsername}{T.RESET}\n"
789 | if self.credPassword:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Password{T.RESET}{' ':6}: {T.GREEN}{self.credPassword}{T.RESET}\n"
790 | if self.credPhone:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Phone{T.RESET}{' ':9}: {T.GREEN}{self.credPhone}{T.RESET}\n"
791 | if self.credCountry:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}>{T.RED} Country{T.RESET}{' ':7}: {T.CYAN}{self.credCountry}{T.RESET}\n"
792 |
793 | if self.VIEW_TYPE == "full":
794 | if self.url:c+=f"{T.MAGENTA}[ {T.YELLOW}URL{T.MAGENTA} ]{T.MAGENTA}> {T.BLUE}{self.url}{T.RESET}\n"
795 | if self.urlPath and self.urlPath != '/':c+=f"{T.MAGENTA}[ {T.YELLOW}URL{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Path{T.RESET}{' ':10}: {T.CYAN}{self.urlPath}{T.RESET}\n"
796 | if self.urlPort and self.urlPort not in [80, 443]:c+=f"{T.MAGENTA}[ {T.YELLOW}URL{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Port{T.RESET}{' ':10}: {T.CYAN}{self.urlPort}{T.RESET}\n"
797 | if self.ip:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.MAGENTA}> {T.BLUE}{self.ip}{T.RESET}\n"
798 | if self.ipPorts:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Ports{T.RESET}{' ':9}: {T.CYAN}{self.ipPorts}{T.RESET}\n"
799 | if self.ipAsn:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Asn{T.RESET}{' ':11}: {T.CYAN}{self.ipAsn}{T.RESET}\n"
800 | if self.ipIsp:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Isp{T.RESET}{' ':11}: {T.CYAN}{self.ipIsp}{T.RESET}\n"
801 | if self.ipOrg:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Org{T.RESET}{' ':11}: {T.CYAN}{self.ipOrg}{T.RESET}\n"
802 | if self.ipCountry:c+=f"{T.MAGENTA}[ {T.YELLOW}IP{T.MAGENTA} ]{T.MAGENTA}>{T.RED} Country{T.RESET}{' ':7}: {T.CYAN}{self.ipCountry}{T.RESET}\n"
803 |
804 | if self.appID:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.CYAN}{self.appID}{T.RESET}\n"
805 | if self.appName:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Name{T.RESET}{' ':10}: {T.CYAN}{self.appName}{T.RESET}\n"
806 | if self.appIcon:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.RED}{T.MAGENTA}> {T.RED} Icon{T.RESET}{' ':10}: {T.CYAN}{self.appIcon}{T.RESET}\n"
807 | if self.appDomain:c+=f"{T.MAGENTA}[ {T.YELLOW}APP{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Domain{T.RESET}{' ':8}: {T.CYAN}{self.appDomain}{T.RESET}\n"
808 |
809 | if self.credEmail:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Email{T.RESET}{' ':9}: {T.GREEN}{self.credEmail}{T.RESET}\n"
810 | if self.credUsername and not self.credEmail:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Username{T.RESET}{' ':6}: {T.GREEN}{self.credUsername}{T.RESET}\n"
811 | if self.credPassword:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Password{T.RESET}{' ':6}: {T.GREEN}{self.credPassword}{T.RESET}\n"
812 | if self.credPhone:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}> {T.RED} Phone{T.RESET}{' ':9}: {T.GREEN}{self.credPhone}{T.RESET}\n"
813 | if self.credCountry:c+=f"{T.MAGENTA}[ {T.YELLOW}CRED{T.MAGENTA} ]{T.MAGENTA}>{T.RED} Country{T.RESET}{' ':7}: {T.CYAN}{self.credCountry}{T.RESET}\n"
814 |
815 | return c
816 |
817 | def save_format(self):
818 | result = []
819 | if self.VIEW_TYPE in ['cred', 'full']:
820 | # 1
821 | if self.url:
822 | result.append(self.url)
823 | elif self.appID:
824 | result.append(self.appID)
825 | else:
826 | result.append('')
827 |
828 | # 2
829 | if self.credUsername:
830 | result.append(self.credUsername)
831 | elif self.credEmail:
832 | result.append(self.credEmail)
833 | else:
834 | result.append('')
835 |
836 | # 3
837 | if self.credPassword:
838 | result.append(self.credPassword)
839 | else:
840 | result.append('')
841 |
842 | # 4
843 | if self.credCountry:
844 | result.append(self.credCountry)
845 | else:
846 | result.append('')
847 |
848 | # 5
849 | #if self.credDate:
850 | # result.append(self.credDate)
851 | #else:
852 | # result.append('')
853 |
854 | return result
855 |
856 | elif self.VIEW_TYPE in ['subdomain', 'related', 'domain']:
857 | return self.domain
858 |
859 | elif self.VIEW_TYPE == 'email':
860 | return self.credEmail
861 |
862 | elif self.VIEW_TYPE == 'phone':
863 | return self.credPhone
864 |
865 | elif self.VIEW_TYPE == 'username':
866 | return self.credUsername
867 |
868 | elif self.VIEW_TYPE == 'password':
869 | return self.credPassword
870 |
871 |
872 | elif self.VIEW_TYPE == 'ip':
873 | return self.ip
874 |
875 | elif self.VIEW_TYPE == 'app':
876 | return self.appID
877 |
878 | elif self.VIEW_TYPE == 'url':
879 | return self.url
880 | else:
881 | return 'null'
882 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | tldextract==5.1.2
2 | yaspin==3.0.1
3 | argcomplete==3.3.0
4 | requests==2.31.0
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from distutils.core import setup
3 | from os import path
4 | here = path.abspath(path.dirname(__file__))
5 | with open(path.join(here, 'README.md'), encoding='utf-8') as f:
6 | long_description = f.read()
7 |
8 | setup(name='chiasmodon',
9 | version='3.0.2',
10 | description='Chiasmodon is an OSINT tool that allows users to gather information from various sources and conduct targeted searches based on domains, Google Play applications, email addresses, IP addresses, organizations, URLs, and more. It provides comprehensive scanning capabilities, customizable output formats, and additional options for enhanced data analysis and customization.',
11 | long_description=long_description,
12 | long_description_content_type='text/markdown',
13 | author='chiasmod0n',
14 | keywords='intelligence osint credentials emails asn cidr bugbounty subdomains information-gathering intelligence-analysis reconnaissance attack-surface subdomain-enumeration reconnaissance-framework bugbounty-tool email-enumeration chiasmodon',
15 | url='https://github.com/chiasmod0n/chiasmodon',
16 | packages=['.'],
17 | scripts=['cli/chiasmodon_cli.py'],
18 | install_requires=['requests', 'yaspin', 'tldextract','argcomplete']
19 | )
20 |
--------------------------------------------------------------------------------