subsurfer 0.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- subsurfer-0.1.8/LICENSE +21 -0
- subsurfer-0.1.8/MANIFEST.in +4 -0
- subsurfer-0.1.8/PKG-INFO +242 -0
- subsurfer-0.1.8/README.md +198 -0
- subsurfer-0.1.8/setup.cfg +4 -0
- subsurfer-0.1.8/setup.py +51 -0
- subsurfer-0.1.8/subsurfer/__init__.py +5 -0
- subsurfer-0.1.8/subsurfer/__main__.py +16 -0
- subsurfer-0.1.8/subsurfer/core/__init__.py +1 -0
- subsurfer-0.1.8/subsurfer/core/cli/__init__.py +1 -0
- subsurfer-0.1.8/subsurfer/core/cli/cli.py +147 -0
- subsurfer-0.1.8/subsurfer/core/cli/parser.py +47 -0
- subsurfer-0.1.8/subsurfer/core/config/__init__.py +1 -0
- subsurfer-0.1.8/subsurfer/core/config/config.yaml +18 -0
- subsurfer-0.1.8/subsurfer/core/controller/__init__.py +0 -0
- subsurfer-0.1.8/subsurfer/core/controller/controller.py +215 -0
- subsurfer-0.1.8/subsurfer/core/handler/__init__.py +0 -0
- subsurfer-0.1.8/subsurfer/core/handler/active/srv.py +89 -0
- subsurfer-0.1.8/subsurfer/core/handler/active/sweep.py +120 -0
- subsurfer-0.1.8/subsurfer/core/handler/active/zone.py +102 -0
- subsurfer-0.1.8/subsurfer/core/handler/active_handler.py +88 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/abuseipdb.py +60 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/alienvault.py +52 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/anubisdb.py +73 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/bufferover.py +99 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/crtsh.py +46 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/digitorus.py +95 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/hackertarget.py +56 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/myssl.py +74 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/shrewdeye.py +52 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/subdomaincenter.py +53 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive/urlscan.py +55 -0
- subsurfer-0.1.8/subsurfer/core/handler/passive_handler.py +103 -0
- subsurfer-0.1.8/subsurfer/core/handler/web/web_scanner.py +136 -0
- subsurfer-0.1.8/subsurfer/subsurfer.py +68 -0
- subsurfer-0.1.8/subsurfer.egg-info/PKG-INFO +242 -0
- subsurfer-0.1.8/subsurfer.egg-info/SOURCES.txt +46 -0
- subsurfer-0.1.8/subsurfer.egg-info/dependency_links.txt +1 -0
- subsurfer-0.1.8/subsurfer.egg-info/entry_points.txt +2 -0
- subsurfer-0.1.8/subsurfer.egg-info/requires.txt +9 -0
- subsurfer-0.1.8/subsurfer.egg-info/top_level.txt +1 -0
- subsurfer-0.1.8/tests/__init__.py +0 -0
- subsurfer-0.1.8/tests/cli/__init__.py +0 -0
- subsurfer-0.1.8/tests/cli/test_cli.py +0 -0
- subsurfer-0.1.8/tests/config/config.yaml +18 -0
- subsurfer-0.1.8/tests/handlers/__init__.py +0 -0
- subsurfer-0.1.8/tests/handlers/test_active_handler.py +78 -0
- subsurfer-0.1.8/tests/handlers/test_passive_handler.py +176 -0
subsurfer-0.1.8/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 arrester
|
|
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.
|
subsurfer-0.1.8/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: subsurfer
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: Red Teaming and Web Bug Bounty Fast Asset Identification Tool
|
|
5
|
+
Home-page: https://github.com/arrester/subsurfer
|
|
6
|
+
Author: arrester
|
|
7
|
+
Author-email: arresterloyal@gmail.com
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/arrester/subsurfer/issues
|
|
9
|
+
Project-URL: Source, https://github.com/arrester/subsurfer
|
|
10
|
+
Project-URL: Documentation, https://github.com/arrester/subsurfer#readme
|
|
11
|
+
Keywords: security,subdomain enumeration,bug bounty,red team,web security
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: rich>=13.7.0
|
|
25
|
+
Requires-Dist: aiohttp>=3.9.1
|
|
26
|
+
Requires-Dist: beautifulsoup4>=4.12.2
|
|
27
|
+
Requires-Dist: dnspython>=2.4.2
|
|
28
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
29
|
+
Requires-Dist: asyncio>=3.4.3
|
|
30
|
+
Requires-Dist: pytest>=7.4.3
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23.2
|
|
32
|
+
Requires-Dist: python-Wappalyzer>=0.3.1
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: description-content-type
|
|
38
|
+
Dynamic: home-page
|
|
39
|
+
Dynamic: keywords
|
|
40
|
+
Dynamic: project-url
|
|
41
|
+
Dynamic: requires-dist
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
Dynamic: summary
|
|
44
|
+
|
|
45
|
+
# πββοΈ SubSurfer
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+

|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
SubSurferλ λΉ λ₯΄κ³ ν¨μ¨μ μΈ μλΈλλ©μΈ μ΄κ±° λ° μΉ μμ° μλ³ λꡬμ
λλ€.
|
|
52
|
+
|
|
53
|
+
<br>
|
|
54
|
+
|
|
55
|
+
## π νΉμ§
|
|
56
|
+
- **λ λν/λ²κ·Έλ°μ΄ν° μ§μ**: λ λν μμ κ³Ό μΉ λ²κ·Έλ°μ΄ν° νλ‘μ νΈ λͺ¨λμμ νμ© κ°λ₯
|
|
57
|
+
- **κ³ μ±λ₯ μ€μΊ**: λΉλκΈ° λ° λ³λ ¬ μ²λ¦¬λ₯Ό ν΅ν λΉ λ₯Έ μλΈλλ©μΈ μμ§
|
|
58
|
+
- **ν¬νΈ μ€μΊ**: μ¬μ©μ μ μ ν¬νΈ λ²μλ‘ μμ° μ€μΊ λ²μ νμ₯
|
|
59
|
+
- **μΉ μλΉμ€ μλ³**: μΉ μλ², κΈ°μ μ€ν λ± νκ²½ μ 보 μμ§
|
|
60
|
+
- **νμ΄νλΌμΈ μ§μ**: `-pipeweb`, `-pipesub` μ΅μ
μΌλ‘ λ€λ₯Έ λꡬμμ μ°κ³ κ°λ₯
|
|
61
|
+
- **λͺ¨λν μ€κ³**: Python λͺ¨λλ‘ importνμ¬ μ¬μ© κ°λ₯
|
|
62
|
+
- **μ§μμ μ
λ°μ΄νΈ**: μλ‘μ΄ passive/active λͺ¨λ μ§μ μΆκ° μμ
|
|
63
|
+
|
|
64
|
+
<br>
|
|
65
|
+
|
|
66
|
+
## π μ€μΉ
|
|
67
|
+
<b>bash</b>
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/arrester/subsurfer.git
|
|
70
|
+
cd subsurfer
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
or <br>
|
|
74
|
+
|
|
75
|
+
<b>Python</b>
|
|
76
|
+
```bash
|
|
77
|
+
pip install -r requirements.txt
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
<br>
|
|
81
|
+
|
|
82
|
+
## π μ¬μ©λ²
|
|
83
|
+
### CLI λͺ¨λ
|
|
84
|
+
<b>κΈ°λ³Έ μ€μΊ</b><br>
|
|
85
|
+
`subsurfer -t vulnweb.com`
|
|
86
|
+
|
|
87
|
+
<b>μ‘ν°λΈ μ€μΊ νμ±ν</b><br>
|
|
88
|
+
`subsurfer -t vulnweb.com -a`
|
|
89
|
+
|
|
90
|
+
<b>ν¬νΈ μ€μΊ ν¬ν¨</b><br>
|
|
91
|
+
`subsurfer -t vulnweb.com -dp` # κΈ°λ³Έ ν¬νΈ <br>
|
|
92
|
+
`subsurfer -t vulnweb.com -p 80,443,8080-8090` # μ¬μ©μ μ μ ν¬νΈ
|
|
93
|
+
|
|
94
|
+
<b>νμ΄νλΌμΈ μΆλ ₯</b><br>
|
|
95
|
+
`subsurfer -t vulnweb.com -pipeweb` # μΉ μλ² κ²°κ³Όλ§ μΆλ ₯ <br>
|
|
96
|
+
`subsurfer -t vulnweb.com -pipesub` # μλΈλλ©μΈ κ²°κ³Όλ§ μΆλ ₯
|
|
97
|
+
|
|
98
|
+
### Python λͺ¨λλ‘ μ¬μ©
|
|
99
|
+
<b>Subdomain Scan</b><br>
|
|
100
|
+
```python
|
|
101
|
+
from subsurfer.core.controller.controller import SubSurferController
|
|
102
|
+
import asyncio
|
|
103
|
+
|
|
104
|
+
async def main():
|
|
105
|
+
controller = SubSurferController(
|
|
106
|
+
target="vulnweb.com",
|
|
107
|
+
verbose=1,
|
|
108
|
+
active=False # Active Scan Option
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# μλΈλλ©μΈ μμ§
|
|
112
|
+
subdomains = await controller.collect_subdomains()
|
|
113
|
+
|
|
114
|
+
# κ²°κ³Ό μΆλ ₯
|
|
115
|
+
print(f"λ°κ²¬λ μλΈλλ©μΈ: {len(subdomains)}κ°")
|
|
116
|
+
for subdomain in sorted(subdomains):
|
|
117
|
+
print(subdomain)
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
asyncio.run(main())
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
<br>
|
|
124
|
+
|
|
125
|
+
<b>Port Scan</b><br>
|
|
126
|
+
```python
|
|
127
|
+
from subsurfer.core.controller.controller import SubSurferController
|
|
128
|
+
import asyncio
|
|
129
|
+
|
|
130
|
+
async def main():
|
|
131
|
+
controller = SubSurferController(
|
|
132
|
+
target="vulnweb.com",
|
|
133
|
+
verbose=1
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# μλΈλλ©μΈ μμ§
|
|
137
|
+
subdomains = await controller.collect_subdomains()
|
|
138
|
+
|
|
139
|
+
# κΈ°λ³Έ 80, 443 μ€μΊ μ€μ
|
|
140
|
+
ports = None
|
|
141
|
+
|
|
142
|
+
# ν¬νΈ μ€μΊ μ€μ
|
|
143
|
+
# ports = controller.parse_ports() # κΈ°λ³Έ ν¬νΈ
|
|
144
|
+
# λλ μ¬μ©μ μ§μ ν¬νΈ
|
|
145
|
+
# ports = controller.parse_ports("80,443,8080-8090")
|
|
146
|
+
|
|
147
|
+
# μΉ μλΉμ€ μ€μΊ
|
|
148
|
+
web_services = await controller.scan_web_services(subdomains, ports)
|
|
149
|
+
|
|
150
|
+
# μΉ μλ² μΆλ ₯
|
|
151
|
+
print("\nμΉ μλ²:")
|
|
152
|
+
for server in sorted(web_services['web_servers']):
|
|
153
|
+
print(f"https://{server}")
|
|
154
|
+
|
|
155
|
+
# νμ±νλ μλΉμ€ μΆλ ₯
|
|
156
|
+
print("\nνμ±νλ μλΉμ€:")
|
|
157
|
+
for service in sorted(web_services['enabled_services']):
|
|
158
|
+
print(service)
|
|
159
|
+
|
|
160
|
+
# URLκ³Ό ν¬νΈ μ 보 μΆλ ₯
|
|
161
|
+
print("\nλ°κ²¬λ URL:")
|
|
162
|
+
for subdomain, urls in web_services['all_urls'].items():
|
|
163
|
+
for url, port in urls:
|
|
164
|
+
print(f"{url}:{port}")
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
asyncio.run(main())
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
<br>
|
|
171
|
+
|
|
172
|
+
<b>Result Save</b><br>
|
|
173
|
+
```python
|
|
174
|
+
from subsurfer.core.controller.controller import SubSurferController
|
|
175
|
+
import asyncio
|
|
176
|
+
|
|
177
|
+
async def main():
|
|
178
|
+
controller = SubSurferController("vulnweb.com")
|
|
179
|
+
|
|
180
|
+
# μλΈλλ©μΈ μμ§ λ° μΉ μλΉμ€ μ€μΊ
|
|
181
|
+
subdomains = await controller.collect_subdomains()
|
|
182
|
+
web_services = await controller.scan_web_services(subdomains)
|
|
183
|
+
|
|
184
|
+
# κ²°κ³Ό μ μ₯
|
|
185
|
+
results_dict = {
|
|
186
|
+
'subdomains': subdomains,
|
|
187
|
+
'web_services': web_services.get('web_services', {}),
|
|
188
|
+
'web_servers': web_services.get('web_servers', set()),
|
|
189
|
+
'enabled_services': web_services.get('enabled_services', set()),
|
|
190
|
+
'all_urls': web_services.get('all_urls', {}) # URLκ³Ό ν¬νΈ μ 보 ν¬ν¨
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# κΈ°λ³Έ κ²°κ³Ό νμΌ κ²½λ‘ μμ± (results λλ ν 리μ μ μ₯)
|
|
194
|
+
output_path = controller.get_output_path()
|
|
195
|
+
controller.save_results(results_dict, output_path)
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
asyncio.run(main())
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
<br>
|
|
202
|
+
|
|
203
|
+
## π§ͺ ν
μ€νΈ
|
|
204
|
+
### ν¨μλΈ νΈλ€λ¬ ν
μ€νΈ
|
|
205
|
+
`pytest tests/handlers/test_passive_handler.py -v`
|
|
206
|
+
|
|
207
|
+
<br>
|
|
208
|
+
|
|
209
|
+
### μ‘ν°λΈ νΈλ€λ¬ ν
μ€νΈ
|
|
210
|
+
`pytest tests/handlers/test_active_handler.py -v`
|
|
211
|
+
|
|
212
|
+
<br>
|
|
213
|
+
|
|
214
|
+
## πΊοΈ ToDo
|
|
215
|
+
### 0.2 λ²μ
|
|
216
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
217
|
+
|
|
218
|
+
### 0.3 λ²μ
|
|
219
|
+
- JSON κ²°κ³Ό μΆλ ₯ μ΅μ
μΆκ°
|
|
220
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
221
|
+
- κΈ°ν κΈ°λ₯ μ
λ°μ΄νΈ
|
|
222
|
+
|
|
223
|
+
### 0.4 λ²μ
|
|
224
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
225
|
+
- μλΈλλ©μΈ νμ·¨ κ²μ¬ κΈ°λ₯
|
|
226
|
+
|
|
227
|
+
### 0.5 λ²μ
|
|
228
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
229
|
+
- μλ‘μ΄ μ‘ν°λΈ λͺ¨λ μΆκ°
|
|
230
|
+
|
|
231
|
+
## π μꡬμ¬ν
|
|
232
|
+
|
|
233
|
+
- Python 3.13.0 μ΄μ κΆμ₯
|
|
234
|
+
- aiohttp
|
|
235
|
+
- rich
|
|
236
|
+
- pytest (ν
μ€νΈμ©)
|
|
237
|
+
|
|
238
|
+
## π λΌμ΄μ μ€
|
|
239
|
+
MIT License
|
|
240
|
+
|
|
241
|
+
## π€ κΈ°μ¬
|
|
242
|
+
Bug Report, Feature Suggestions, Pull Request
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# πββοΈ SubSurfer
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
SubSurferλ λΉ λ₯΄κ³ ν¨μ¨μ μΈ μλΈλλ©μΈ μ΄κ±° λ° μΉ μμ° μλ³ λꡬμ
λλ€.
|
|
8
|
+
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
## π νΉμ§
|
|
12
|
+
- **λ λν/λ²κ·Έλ°μ΄ν° μ§μ**: λ λν μμ κ³Ό μΉ λ²κ·Έλ°μ΄ν° νλ‘μ νΈ λͺ¨λμμ νμ© κ°λ₯
|
|
13
|
+
- **κ³ μ±λ₯ μ€μΊ**: λΉλκΈ° λ° λ³λ ¬ μ²λ¦¬λ₯Ό ν΅ν λΉ λ₯Έ μλΈλλ©μΈ μμ§
|
|
14
|
+
- **ν¬νΈ μ€μΊ**: μ¬μ©μ μ μ ν¬νΈ λ²μλ‘ μμ° μ€μΊ λ²μ νμ₯
|
|
15
|
+
- **μΉ μλΉμ€ μλ³**: μΉ μλ², κΈ°μ μ€ν λ± νκ²½ μ 보 μμ§
|
|
16
|
+
- **νμ΄νλΌμΈ μ§μ**: `-pipeweb`, `-pipesub` μ΅μ
μΌλ‘ λ€λ₯Έ λꡬμμ μ°κ³ κ°λ₯
|
|
17
|
+
- **λͺ¨λν μ€κ³**: Python λͺ¨λλ‘ importνμ¬ μ¬μ© κ°λ₯
|
|
18
|
+
- **μ§μμ μ
λ°μ΄νΈ**: μλ‘μ΄ passive/active λͺ¨λ μ§μ μΆκ° μμ
|
|
19
|
+
|
|
20
|
+
<br>
|
|
21
|
+
|
|
22
|
+
## π μ€μΉ
|
|
23
|
+
<b>bash</b>
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/arrester/subsurfer.git
|
|
26
|
+
cd subsurfer
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
or <br>
|
|
30
|
+
|
|
31
|
+
<b>Python</b>
|
|
32
|
+
```bash
|
|
33
|
+
pip install -r requirements.txt
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
<br>
|
|
37
|
+
|
|
38
|
+
## π μ¬μ©λ²
|
|
39
|
+
### CLI λͺ¨λ
|
|
40
|
+
<b>κΈ°λ³Έ μ€μΊ</b><br>
|
|
41
|
+
`subsurfer -t vulnweb.com`
|
|
42
|
+
|
|
43
|
+
<b>μ‘ν°λΈ μ€μΊ νμ±ν</b><br>
|
|
44
|
+
`subsurfer -t vulnweb.com -a`
|
|
45
|
+
|
|
46
|
+
<b>ν¬νΈ μ€μΊ ν¬ν¨</b><br>
|
|
47
|
+
`subsurfer -t vulnweb.com -dp` # κΈ°λ³Έ ν¬νΈ <br>
|
|
48
|
+
`subsurfer -t vulnweb.com -p 80,443,8080-8090` # μ¬μ©μ μ μ ν¬νΈ
|
|
49
|
+
|
|
50
|
+
<b>νμ΄νλΌμΈ μΆλ ₯</b><br>
|
|
51
|
+
`subsurfer -t vulnweb.com -pipeweb` # μΉ μλ² κ²°κ³Όλ§ μΆλ ₯ <br>
|
|
52
|
+
`subsurfer -t vulnweb.com -pipesub` # μλΈλλ©μΈ κ²°κ³Όλ§ μΆλ ₯
|
|
53
|
+
|
|
54
|
+
### Python λͺ¨λλ‘ μ¬μ©
|
|
55
|
+
<b>Subdomain Scan</b><br>
|
|
56
|
+
```python
|
|
57
|
+
from subsurfer.core.controller.controller import SubSurferController
|
|
58
|
+
import asyncio
|
|
59
|
+
|
|
60
|
+
async def main():
|
|
61
|
+
controller = SubSurferController(
|
|
62
|
+
target="vulnweb.com",
|
|
63
|
+
verbose=1,
|
|
64
|
+
active=False # Active Scan Option
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# μλΈλλ©μΈ μμ§
|
|
68
|
+
subdomains = await controller.collect_subdomains()
|
|
69
|
+
|
|
70
|
+
# κ²°κ³Ό μΆλ ₯
|
|
71
|
+
print(f"λ°κ²¬λ μλΈλλ©μΈ: {len(subdomains)}κ°")
|
|
72
|
+
for subdomain in sorted(subdomains):
|
|
73
|
+
print(subdomain)
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
<br>
|
|
80
|
+
|
|
81
|
+
<b>Port Scan</b><br>
|
|
82
|
+
```python
|
|
83
|
+
from subsurfer.core.controller.controller import SubSurferController
|
|
84
|
+
import asyncio
|
|
85
|
+
|
|
86
|
+
async def main():
|
|
87
|
+
controller = SubSurferController(
|
|
88
|
+
target="vulnweb.com",
|
|
89
|
+
verbose=1
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# μλΈλλ©μΈ μμ§
|
|
93
|
+
subdomains = await controller.collect_subdomains()
|
|
94
|
+
|
|
95
|
+
# κΈ°λ³Έ 80, 443 μ€μΊ μ€μ
|
|
96
|
+
ports = None
|
|
97
|
+
|
|
98
|
+
# ν¬νΈ μ€μΊ μ€μ
|
|
99
|
+
# ports = controller.parse_ports() # κΈ°λ³Έ ν¬νΈ
|
|
100
|
+
# λλ μ¬μ©μ μ§μ ν¬νΈ
|
|
101
|
+
# ports = controller.parse_ports("80,443,8080-8090")
|
|
102
|
+
|
|
103
|
+
# μΉ μλΉμ€ μ€μΊ
|
|
104
|
+
web_services = await controller.scan_web_services(subdomains, ports)
|
|
105
|
+
|
|
106
|
+
# μΉ μλ² μΆλ ₯
|
|
107
|
+
print("\nμΉ μλ²:")
|
|
108
|
+
for server in sorted(web_services['web_servers']):
|
|
109
|
+
print(f"https://{server}")
|
|
110
|
+
|
|
111
|
+
# νμ±νλ μλΉμ€ μΆλ ₯
|
|
112
|
+
print("\nνμ±νλ μλΉμ€:")
|
|
113
|
+
for service in sorted(web_services['enabled_services']):
|
|
114
|
+
print(service)
|
|
115
|
+
|
|
116
|
+
# URLκ³Ό ν¬νΈ μ 보 μΆλ ₯
|
|
117
|
+
print("\nλ°κ²¬λ URL:")
|
|
118
|
+
for subdomain, urls in web_services['all_urls'].items():
|
|
119
|
+
for url, port in urls:
|
|
120
|
+
print(f"{url}:{port}")
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
asyncio.run(main())
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
<br>
|
|
127
|
+
|
|
128
|
+
<b>Result Save</b><br>
|
|
129
|
+
```python
|
|
130
|
+
from subsurfer.core.controller.controller import SubSurferController
|
|
131
|
+
import asyncio
|
|
132
|
+
|
|
133
|
+
async def main():
|
|
134
|
+
controller = SubSurferController("vulnweb.com")
|
|
135
|
+
|
|
136
|
+
# μλΈλλ©μΈ μμ§ λ° μΉ μλΉμ€ μ€μΊ
|
|
137
|
+
subdomains = await controller.collect_subdomains()
|
|
138
|
+
web_services = await controller.scan_web_services(subdomains)
|
|
139
|
+
|
|
140
|
+
# κ²°κ³Ό μ μ₯
|
|
141
|
+
results_dict = {
|
|
142
|
+
'subdomains': subdomains,
|
|
143
|
+
'web_services': web_services.get('web_services', {}),
|
|
144
|
+
'web_servers': web_services.get('web_servers', set()),
|
|
145
|
+
'enabled_services': web_services.get('enabled_services', set()),
|
|
146
|
+
'all_urls': web_services.get('all_urls', {}) # URLκ³Ό ν¬νΈ μ 보 ν¬ν¨
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# κΈ°λ³Έ κ²°κ³Ό νμΌ κ²½λ‘ μμ± (results λλ ν 리μ μ μ₯)
|
|
150
|
+
output_path = controller.get_output_path()
|
|
151
|
+
controller.save_results(results_dict, output_path)
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
asyncio.run(main())
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
<br>
|
|
158
|
+
|
|
159
|
+
## π§ͺ ν
μ€νΈ
|
|
160
|
+
### ν¨μλΈ νΈλ€λ¬ ν
μ€νΈ
|
|
161
|
+
`pytest tests/handlers/test_passive_handler.py -v`
|
|
162
|
+
|
|
163
|
+
<br>
|
|
164
|
+
|
|
165
|
+
### μ‘ν°λΈ νΈλ€λ¬ ν
μ€νΈ
|
|
166
|
+
`pytest tests/handlers/test_active_handler.py -v`
|
|
167
|
+
|
|
168
|
+
<br>
|
|
169
|
+
|
|
170
|
+
## πΊοΈ ToDo
|
|
171
|
+
### 0.2 λ²μ
|
|
172
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
173
|
+
|
|
174
|
+
### 0.3 λ²μ
|
|
175
|
+
- JSON κ²°κ³Ό μΆλ ₯ μ΅μ
μΆκ°
|
|
176
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
177
|
+
- κΈ°ν κΈ°λ₯ μ
λ°μ΄νΈ
|
|
178
|
+
|
|
179
|
+
### 0.4 λ²μ
|
|
180
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
181
|
+
- μλΈλλ©μΈ νμ·¨ κ²μ¬ κΈ°λ₯
|
|
182
|
+
|
|
183
|
+
### 0.5 λ²μ
|
|
184
|
+
- μλ‘μ΄ ν¨μλΈ λͺ¨λ μΆκ°
|
|
185
|
+
- μλ‘μ΄ μ‘ν°λΈ λͺ¨λ μΆκ°
|
|
186
|
+
|
|
187
|
+
## π μꡬμ¬ν
|
|
188
|
+
|
|
189
|
+
- Python 3.13.0 μ΄μ κΆμ₯
|
|
190
|
+
- aiohttp
|
|
191
|
+
- rich
|
|
192
|
+
- pytest (ν
μ€νΈμ©)
|
|
193
|
+
|
|
194
|
+
## π λΌμ΄μ μ€
|
|
195
|
+
MIT License
|
|
196
|
+
|
|
197
|
+
## π€ κΈ°μ¬
|
|
198
|
+
Bug Report, Feature Suggestions, Pull Request
|
subsurfer-0.1.8/setup.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from setuptools import setup, find_namespace_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="subsurfer",
|
|
5
|
+
version="0.1.8",
|
|
6
|
+
description="Red Teaming and Web Bug Bounty Fast Asset Identification Tool",
|
|
7
|
+
long_description=open('README.md').read(),
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
author="arrester",
|
|
10
|
+
author_email="arresterloyal@gmail.com",
|
|
11
|
+
url="https://github.com/arrester/subsurfer",
|
|
12
|
+
packages=find_namespace_packages(include=['subsurfer*']),
|
|
13
|
+
package_data={
|
|
14
|
+
'subsurfer': ['core/config/*.yaml'],
|
|
15
|
+
},
|
|
16
|
+
include_package_data=True,
|
|
17
|
+
install_requires=[
|
|
18
|
+
'rich>=13.7.0',
|
|
19
|
+
'aiohttp>=3.9.1',
|
|
20
|
+
'beautifulsoup4>=4.12.2',
|
|
21
|
+
'dnspython>=2.4.2',
|
|
22
|
+
'pyyaml>=6.0.1',
|
|
23
|
+
'asyncio>=3.4.3',
|
|
24
|
+
'pytest>=7.4.3',
|
|
25
|
+
'pytest-asyncio>=0.23.2',
|
|
26
|
+
'python-Wappalyzer>=0.3.1'
|
|
27
|
+
],
|
|
28
|
+
entry_points={
|
|
29
|
+
'console_scripts': [
|
|
30
|
+
'subsurfer=subsurfer.__main__:run_main',
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
python_requires='>=3.9',
|
|
34
|
+
classifiers=[
|
|
35
|
+
'Development Status :: 3 - Alpha',
|
|
36
|
+
'Intended Audience :: Information Technology',
|
|
37
|
+
'Intended Audience :: System Administrators',
|
|
38
|
+
'Topic :: Security',
|
|
39
|
+
'Topic :: Internet :: WWW/HTTP',
|
|
40
|
+
'License :: OSI Approved :: MIT License',
|
|
41
|
+
'Programming Language :: Python :: 3',
|
|
42
|
+
'Programming Language :: Python :: 3.13',
|
|
43
|
+
'Operating System :: OS Independent',
|
|
44
|
+
],
|
|
45
|
+
keywords='security, subdomain enumeration, bug bounty, red team, web security',
|
|
46
|
+
project_urls={
|
|
47
|
+
'Bug Reports': 'https://github.com/arrester/subsurfer/issues',
|
|
48
|
+
'Source': 'https://github.com/arrester/subsurfer',
|
|
49
|
+
'Documentation': 'https://github.com/arrester/subsurfer#readme',
|
|
50
|
+
},
|
|
51
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
SubSurfer main entry point
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from subsurfer.subsurfer import main
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
def run_main():
|
|
12
|
+
"""Wrapper function to run the async main"""
|
|
13
|
+
asyncio.run(main())
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
run_main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Empty file to make the directory a Python package
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Empty file to make the directory a Python package
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
CLI utilities module
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
from rich.box import ROUNDED
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
def is_cli_mode():
|
|
18
|
+
"""Check if running in CLI mode"""
|
|
19
|
+
return sys.stdin.isatty()
|
|
20
|
+
|
|
21
|
+
def print_banner(force=False):
|
|
22
|
+
"""Print banner only in CLI mode unless forced"""
|
|
23
|
+
if not is_cli_mode() and not force:
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
banner = r"""
|
|
27
|
+
πββοΈ SubSurfer π
|
|
28
|
+
----------------------
|
|
29
|
+
_____ _ _____ __
|
|
30
|
+
/ ___| | | / ___| / _|
|
|
31
|
+
\ `--. _ _ | |__ \ `--. _ _ _ __ | |_ ___ _ __
|
|
32
|
+
`--. \| | | || '_ \ `--. \| | | || '__|| _| / _ \| '__|
|
|
33
|
+
/\__/ /| |_| || |_) |/\__/ /| |_| || | | | | __/| |
|
|
34
|
+
\____/ \__,_||_.__/ \____/ \__,_||_| |_| \___||_| v0.1
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# λ°°λ ν¨λ μμ±
|
|
38
|
+
banner_panel = Panel(
|
|
39
|
+
banner,
|
|
40
|
+
title="[bold cyan]Red Teaming and Web Bug Bounty Fast Asset Identification Tool[/]",
|
|
41
|
+
subtitle="[bold blue]by. arrester (https://github.com/arrester/subsurfer)[/]",
|
|
42
|
+
style="bold blue",
|
|
43
|
+
box=ROUNDED
|
|
44
|
+
)
|
|
45
|
+
console.print(banner_panel)
|
|
46
|
+
|
|
47
|
+
def print_usage():
|
|
48
|
+
"""Print usage information"""
|
|
49
|
+
usage_table = Table(
|
|
50
|
+
title="[bold cyan]SubSurfer Usage Guide[/]",
|
|
51
|
+
box=ROUNDED,
|
|
52
|
+
show_header=True,
|
|
53
|
+
header_style="bold magenta"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# ν
μ΄λΈ μ»¬λΌ μ€μ
|
|
57
|
+
usage_table.add_column("Command", style="cyan", justify="left")
|
|
58
|
+
usage_table.add_column("Description", style="white", justify="left")
|
|
59
|
+
usage_table.add_column("Example", style="green", justify="left")
|
|
60
|
+
|
|
61
|
+
# μ¬μ©λ² μΆκ°
|
|
62
|
+
usage_table.add_row(
|
|
63
|
+
"subsurfer -t <domain>",
|
|
64
|
+
"Scan single domain",
|
|
65
|
+
"subsurfer -t vulnweb.com"
|
|
66
|
+
)
|
|
67
|
+
usage_table.add_row(
|
|
68
|
+
"subsurfer -t <domain> -o <file>",
|
|
69
|
+
"Save results to file",
|
|
70
|
+
"subsurfer -t vulnweb.com -o results.txt"
|
|
71
|
+
)
|
|
72
|
+
usage_table.add_row(
|
|
73
|
+
"subsurfer -t <domain> -a",
|
|
74
|
+
"Enable active scanning",
|
|
75
|
+
"subsurfer -t vulnweb.com -a"
|
|
76
|
+
)
|
|
77
|
+
usage_table.add_row(
|
|
78
|
+
"subsurfer -t <domain> -v",
|
|
79
|
+
"Increase output verbosity",
|
|
80
|
+
"subsurfer -t vulnweb.com -v"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# μ΅μ
ν
μ΄λΈ μμ±
|
|
84
|
+
options_table = Table(
|
|
85
|
+
title="[bold cyan]Available Options[/]",
|
|
86
|
+
box=ROUNDED,
|
|
87
|
+
show_header=True,
|
|
88
|
+
header_style="bold magenta"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# μ΅μ
ν
μ΄λΈ μ»¬λΌ μ€μ
|
|
92
|
+
options_table.add_column("Option", style="cyan", justify="left")
|
|
93
|
+
options_table.add_column("Description", style="white", justify="left")
|
|
94
|
+
|
|
95
|
+
# μ΅μ
μΆκ° (νμ¬ κ΅¬νλ μ΅μ
λ€λ§)
|
|
96
|
+
options_table.add_row("-h, --help", "Show this help message")
|
|
97
|
+
options_table.add_row("-t, --target", "Target domain (e.g. vulnweb.com)")
|
|
98
|
+
options_table.add_row("-o, --output", "Output file to save results")
|
|
99
|
+
options_table.add_row("-v, --verbose", "Increase output verbosity (-v, -vv, -vvv)")
|
|
100
|
+
options_table.add_row("-a, --active", "Enable active scanning (default: passive only)")
|
|
101
|
+
options_table.add_row("-dp, --default-ports", "Scan default ports")
|
|
102
|
+
options_table.add_row("-p, --port", "Custom port range (e.g. 1-65535)")
|
|
103
|
+
options_table.add_row("-pipeweb", "Output web server results for pipeline")
|
|
104
|
+
options_table.add_row("-pipesub", "Output subdomain results for pipeline")
|
|
105
|
+
options_table.add_row("-to, --takeover", "[Coming Soon] Subdomain takeover detection")
|
|
106
|
+
|
|
107
|
+
# μΆλ ₯
|
|
108
|
+
console.print("\n[bold cyan]Description:[/]")
|
|
109
|
+
console.print("SubSurfer is a fast subdomain enumeration tool that combines both passive and active scanning techniques to discover subdomains of a target domain.\n")
|
|
110
|
+
|
|
111
|
+
console.print(usage_table)
|
|
112
|
+
console.print("\n")
|
|
113
|
+
console.print(options_table)
|
|
114
|
+
console.print("\n[bold cyan]Note:[/] Some scanners may require API keys. Configure them in config.yaml")
|
|
115
|
+
console.print("[bold yellow]Coming Soon:[/] Subdomain takeover detection will be available in the next version!\n")
|
|
116
|
+
|
|
117
|
+
def print_status(message, status="info", cli_only=True):
|
|
118
|
+
"""Print status messages with color coding"""
|
|
119
|
+
if cli_only and not is_cli_mode():
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
colors = {
|
|
123
|
+
"info": "blue",
|
|
124
|
+
"success": "green",
|
|
125
|
+
"warning": "yellow",
|
|
126
|
+
"error": "red"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# μν μμ΄μ½ μΆκ°
|
|
130
|
+
icons = {
|
|
131
|
+
"info": "βΉοΈ",
|
|
132
|
+
"success": "β
",
|
|
133
|
+
"warning": "β οΈ",
|
|
134
|
+
"error": "β"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.print(f"[bold {colors[status]}]{icons[status]} {message}[/]")
|
|
138
|
+
|
|
139
|
+
def main():
|
|
140
|
+
"""Main entry point for CLI"""
|
|
141
|
+
if len(sys.argv) == 1:
|
|
142
|
+
print_banner(force=True)
|
|
143
|
+
print_usage()
|
|
144
|
+
sys.exit(0)
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
main()
|