Skip to content

Commit a1e6b34

Browse files
committed
Optimizations + removed reliance on the X-Debug-Token header
1 parent 7b85ee1 commit a1e6b34

File tree

10 files changed

+60
-50
lines changed

10 files changed

+60
-50
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea
2+
__pycache__

README.md

+1-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ usage: eos [-h] [-V] [-v] [--no-colors] {scan,sources,get,creds,cookies} ...
4242
█████╗ ██║ ██║███████╗
4343
██╔══╝ ██║ ██║╚════██║
4444
███████╗╚██████╔╝███████║ Enemies Of Symfony
45-
╚══════╝ ╚═════╝ ╚══════╝ v1.0.0
45+
╚══════╝ ╚═════╝ ╚══════╝ v1.1
4646

4747
positional arguments:
4848
{scan,sources,get,creds,cookies}
@@ -60,7 +60,6 @@ optional arguments:
6060

6161
examples:
6262
eos scan http://localhost
63-
eos scan -v -t 4 http://localhost
6463
eos scan --headers 'Cookie: foo=bar; john=doe' 'User-Agent: EOS' -- http://localhost
6564
eos get http://localhost config/services.yaml
6665
eos cookies -u jane_admin -H '$2y$13$IMalnQpo7xfZD5FJGbEadOcqyj2mi/NQbQiI8v2wBXfjZ4nwshJlG' -s 67d829bf61dc5f87a73fd814e2c9f629
@@ -71,9 +70,6 @@ $ eos scan http://localhost --output results
7170
[+] Starting scan on http://localhost
7271
[+] 2020-04-23 14:21:26.463352 is a great day
7372

74-
[+] Checks
75-
[!] Target found in debug mode
76-
7773
[+] Info
7874
[!] Symfony 5.0.1
7975
[!] PHP 7.3.11-1~deb10u1

eos/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
EOS Package.
33
"""
44

5-
__version__ = '1.0.0'
5+
__version__ = '1.1'
66
__banner__ = """
77
███████╗ ██████╗ ███████╗
88
██╔════╝██╔═══██╗██╔════╝

eos/__main__.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
scan_examples = [
2222
'eos scan http://localhost',
23-
'eos scan -v -t 4 http://localhost',
2423
"eos scan --headers 'Cookie: foo=bar; john=doe' 'User-Agent: EOS' -- http://localhost",
2524
]
2625

@@ -45,7 +44,7 @@ class CLI:
4544
def scan(cls, args):
4645
"""Scan handler."""
4746
eos = EOS(url=args.url, output=args.output, session=args.session)
48-
eos.run(check_only=args.check_only, threads=args.threads)
47+
eos.run(threads=args.threads)
4948

5049
@classmethod
5150
def sources(cls, args):
@@ -140,7 +139,6 @@ def main():
140139
scan = sub.add_parser('scan', component='Scanner', help='perform a full scan', examples=scan_examples,
141140
parents=[common, output, threads])
142141
scan.add_argument('--timestamps', action='store_true', help='log with timestamps')
143-
scan.add_argument('--check-only', action='store_true', help='only check if target is vulnerable')
144142
scan.set_defaults(handler=CLI.scan)
145143

146144
# Sources

eos/core/engine/worker.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def process(self, request):
6666
:param request: the unprepared request to process
6767
"""
6868

69-
response = self.session.send(request.prepare())
69+
response = self.session.request(
70+
method=request.method, url=request.url, params=request.params, data=request.data)
7071
response.request = request
7172
self.log.debug('[%d] %s', response.status_code, response.url)
7273
self.results.append(response)

eos/core/eos.py

+11-18
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,17 @@ def requests(self):
4343
"""List of previously issued requests."""
4444
return self.symfony.requests
4545

46-
def run(self, check_only=False, wordlists=None, threads=10):
46+
def run(self, wordlists=None, threads=10):
4747
"""
4848
Run scan.
4949
50-
1. Ensure the target is reachable and in debug mode
50+
1. Start engine
5151
2. Load plugins and run them
5252
3. Output tokens generated from issued requests
5353
5454
Scans can be performed in aggressive mode where aggressive plugins will be run.
5555
The engine can be configured using a different wordlists and a specific number of threads.
5656
57-
:param check_only: only perform checks on target, do not run plugins
5857
:param wordlists: wordlists file or directory
5958
:param threads: number of workers to run simultaneously
6059
"""
@@ -64,21 +63,15 @@ def run(self, check_only=False, wordlists=None, threads=10):
6463
self.log.info('%s is a great day', start)
6564
wordlists = wordlists or self.wordlists
6665

67-
# Checks
68-
print()
69-
self.check()
70-
71-
# Stop if only check
72-
if not check_only:
73-
# Start engine, load and run plugins
74-
engine = Engine(threads, session=self.session)
75-
engine.start()
76-
try:
77-
options = dict(wordlists=wordlists, output=self.output)
78-
manager = PluginManager(symfony=self.symfony, engine=engine, **options)
79-
manager.run()
80-
finally:
81-
engine.stop()
66+
# Start engine, load and run plugins
67+
engine = Engine(threads, session=self.session)
68+
engine.start()
69+
try:
70+
options = dict(wordlists=wordlists, output=self.output)
71+
manager = PluginManager(symfony=self.symfony, engine=engine, **options)
72+
manager.run()
73+
finally:
74+
engine.stop()
8275

8376
if self.symfony.files and self.output:
8477
print()

eos/core/profiler.py

+17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
Contains a Profiler class for communicating with the Symfony profiler.
55
"""
66

7+
import csv
8+
from io import StringIO
79
from pathlib import Path
810
from urllib.parse import urljoin
911

@@ -22,6 +24,7 @@ class Profiler(Base):
2224
"""
2325

2426
path = '_profiler'
27+
cache_path = 'profiler/index.csv'
2528

2629
def __init__(self, url, session=None):
2730
"""
@@ -32,6 +35,7 @@ def __init__(self, url, session=None):
3235

3336
self._url = urljoin(url + '/', self.path)
3437
self.session = session or Session()
38+
self._cache = None
3539

3640
def get(self, token='', **kwargs):
3741
"""
@@ -85,3 +89,16 @@ def parse_file_preview(data):
8589
def url(self, token=''):
8690
"""URL builder."""
8791
return urljoin(self._url + '/' if token else self._url, token)
92+
93+
def cache(self, symfony_cache=None, refresh=True):
94+
if refresh or self._cache is None:
95+
cache_path = str(Path(symfony_cache, self.cache_path))
96+
index = self.open(cache_path)
97+
self._cache = list(csv.reader(StringIO(index)))
98+
return self._cache
99+
100+
def logs(self, filters=None, symfony_cache=None, refresh=True):
101+
all = lambda token, ip, method, url, timestamp, x, code: True
102+
filters = filters or all
103+
wrapper = lambda row: filters(*row)
104+
return list(filter(wrapper, self.cache(symfony_cache, refresh)))

eos/plugins/info.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def run(self):
3535
"""
3636

3737
# Perform request, and parse it
38-
r = self.symfony.profiler.get(self.token, params={'panel': 'config'})
38+
r = self.symfony.profiler.get('latest', params={'panel': 'config'})
3939
soup = self.parse(r.text)
4040

4141
# Version

eos/plugins/logs.py

+2-15
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
"""Request logs EOS plugin."""
22

33
import re
4-
import csv
5-
from io import StringIO
6-
from pathlib import Path
74
from datetime import datetime
85
from urllib.parse import parse_qs
96

@@ -65,7 +62,6 @@ class Plugin(AbstractPlugin):
6562
"""
6663

6764
name = 'Request logs'
68-
_cache = 'profiler/index.csv'
6965

7066
usernames = ['user', 'login', 'usr', 'email']
7167
"""List of possible username parameter keys."""
@@ -84,13 +80,8 @@ def run(self):
8480
"""
8581

8682
# Get list of POST requests
87-
index = self.symfony.profiler.open(self.cache)
88-
if index is not None:
89-
self.symfony.files[self.cache] = index
90-
rows = csv.reader(StringIO(index))
91-
logs = list(row for row in rows if row and row[2] == 'POST')
92-
else:
93-
logs = self.list(self.limit)
83+
filters = lambda token, ip, method, url, timestamp, x, code: method == 'POST'
84+
logs = self.symfony.profiler.logs(filters, self.symfony.cache, refresh=False)
9485

9586
# No POST requests
9687
if not logs:
@@ -150,10 +141,6 @@ def run(self):
150141
for user in users_without_session:
151142
self.log.warning(' %s: %s', user.name, user.password)
152143

153-
@property
154-
def cache(self):
155-
return str(Path(self.symfony.cache, self._cache))
156-
157144
def list(self, limit, method='POST'):
158145
"""
159146
Get the requests list.

eos/plugins/routes.py

+22-6
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,32 @@ def run(self):
3737
prevent parameterized routes from matching it.
3838
"""
3939

40+
token = None
41+
42+
# Find 404 in cache
43+
filters = lambda token, ip, method, url, timestamp, x, code: code == 404
44+
logs = self.symfony.profiler.logs(filters, self.symfony.cache, refresh=False)
45+
4046
# Trigger and ensure a 404
41-
self.log.debug('Triggering a 404 error')
42-
rand = '/'.join(choice(ascii_lowercase) for _ in range(12))
43-
r = self.symfony.get(rand)
44-
if r.status_code != 404:
45-
self.log.error('Target responded with status code %d!', r.status_code)
47+
if not logs:
48+
self.log.debug('Triggering a 404 error')
49+
rand = '/'.join(choice(ascii_lowercase) for _ in range(12))
50+
r = self.symfony.get(rand)
51+
token = r.token
52+
if not r.token:
53+
mark = self.symfony.url(rand)
54+
filters = lambda token, ip, method, url, timestamp, x, code: url == mark
55+
logs = self.symfony.profiler.logs(filters, self.symfony.cache, refresh=True)
56+
57+
if not logs and not token:
58+
self.log.warning('Could not find any suitable 404 response')
4659
return
4760

61+
if not token:
62+
token = logs[-1][0]
63+
4864
# Check that no route has matched
49-
r = self.symfony.profiler.get(r.token, params={'panel': 'router'})
65+
r = self.symfony.profiler.get(token, params={'panel': 'router'})
5066
soup = self.parse(r.text)
5167
label = soup.find('span', class_='label', string='Matched route')
5268
route = label.find_previous('span', class_='value').text

0 commit comments

Comments
 (0)