Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions core/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,41 @@ def _extract_edge_id(self, url: str) -> Optional[str]:

return None

def _extract_chrome_id(self, url: str) -> Optional[str]:
"""Extract Chrome extension ID from URL."""
# Handle both old and new Chrome Web Store URLs
if 'chrome.google.com/webstore' in url or 'chromewebstore.google.com/detail' in url:
# Split by '/' and get the last part which contains the ID
parts = url.rstrip('/').split('/')
return parts[-1]
# If the input is already an ID, return it directly
elif len(url.strip()) == 32:
return url.strip()
return None

def download_chrome(self, ext_id: str, name: Optional[str] = None) -> Optional[str]:
"""Download Chrome extension using the updated URL format.

Args:
ext_id: The Chrome extension ID
ext_id: The Chrome extension ID or URL
name: Optional name for the saved file

Returns:
The name of the saved file if successful, None otherwise
"""
save_name = name if name else ext_id
# Extract extension ID if a URL is provided
actual_id = self._extract_chrome_id(ext_id)
if not actual_id:
core.updatelog('Invalid Chrome extension ID or URL')
return None

save_name = name if name else actual_id

dl_url = (
"https://clients2.google.com/service/update2/crx?"
"response=redirect&"
f"prodversion={self.chrome_version}&"
"x=id%3D" + ext_id + "%26installsource%3Dondemand%26uc&"
f"x=id%3D{actual_id}%26installsource%3Dondemand%26uc&"
f"nacl_arch={self.nacl_arch}&"
"acceptformat=crx2,crx3"
)
Expand Down
17 changes: 14 additions & 3 deletions extanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import core.helper as helper
import core.settings as settings
from flask_wtf.csrf import CSRFProtect
from frontend.api import api # Import the API blueprint

parser = argparse.ArgumentParser(prog='extanalysis.py', add_help=False)
parser.add_argument('-h', '--host', help='Host to run ExtAnalysis on. Default host is 127.0.0.1')
Expand Down Expand Up @@ -88,6 +89,7 @@ def allowed_file(filename):
app = Flask('ExtAnalysis - Browser Extension Analysis Toolkit')
app.config['UPLOAD_FOLDER'] = core.lab_path
app.secret_key = str(os.urandom(24))
app.register_blueprint(api, url_prefix='/api') # Register the blueprint
csrf.init_app(app)


Expand Down Expand Up @@ -176,10 +178,19 @@ def source_code(url):
return (vs.view(url))


@app.route('/analysis/<analysis_id>')
@app.route("/analysis/<analysis_id>/")
def show_analysis(analysis_id):
import frontend.viewresult as viewResult
return (viewResult.view(analysis_id))
return redirect(url_for('view_tab', analysis_id=analysis_id, tab='basic_info'))

@app.route("/analysis/<analysis_id>/<tab>/")
def view_tab(analysis_id, tab):
valid_tabs = ['basic_info', 'files', 'permissions', 'urls_domains', 'gathered_intels']
if tab not in valid_tabs:
return redirect(url_for('view_tab', analysis_id=analysis_id, tab='basic_info'))

# Import the view function from frontend module
import frontend.viewresult as viewresult
return viewresult.view(analysis_id, tab)


if __name__ == "__main__":
Expand Down
86 changes: 84 additions & 2 deletions frontend/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,97 @@
import core.downloader as download_extension
import os
import json
from flask import Flask, request, render_template, redirect, url_for, send_from_directory
from flask import Flask, request, render_template, redirect, url_for, send_from_directory, Blueprint, jsonify
import logging
import traceback
import core.scans as scan
import base64
from .handlers.result_handler import view_result
from .handlers.data_handler import (
get_basic_info, get_files_data, get_urls_data,
get_permissions_data, get_domains_data, get_ips_data,
get_emails_data, get_btc_data, get_comments_data,
get_base64_data, get_manifest_data
)

api = Blueprint('api', __name__)

@api.route('/result/<analysis_id>', methods=['GET'])
def view_analysis_result(analysis_id):
return view_result(analysis_id)

@api.route('/result/<analysis_id>/basic_info', methods=['GET'])
def basic_info(analysis_id):
return jsonify(get_basic_info(analysis_id))

@api.route('/result/<analysis_id>/files', methods=['GET'])
def files(analysis_id):
return jsonify(get_files_data(analysis_id))

@api.route('/result/<analysis_id>/urls', methods=['GET'])
def urls(analysis_id):
return jsonify(get_urls_data(analysis_id))

@api.route('/result/<analysis_id>/permissions', methods=['GET'])
def permissions(analysis_id):
return jsonify(get_permissions_data(analysis_id))

@api.route('/result/<analysis_id>/domains', methods=['GET'])
def domains(analysis_id):
return jsonify(get_domains_data(analysis_id))

@api.route('/result/<analysis_id>/ips', methods=['GET'])
def ips(analysis_id):
return jsonify(get_ips_data(analysis_id))

@api.route('/result/<analysis_id>/emails', methods=['GET'])
def emails(analysis_id):
return jsonify(get_emails_data(analysis_id))

@api.route('/result/<analysis_id>/btc', methods=['GET'])
def btc(analysis_id):
return jsonify(get_btc_data(analysis_id))

@api.route('/result/<analysis_id>/comments', methods=['GET'])
def comments(analysis_id):
return jsonify(get_comments_data(analysis_id))

@api.route('/result/<analysis_id>/base64', methods=['GET'])
def base64(analysis_id):
return jsonify(get_base64_data(analysis_id))

@api.route('/result/<analysis_id>/manifest', methods=['GET'])
def manifest(analysis_id):
return jsonify(get_manifest_data(analysis_id))

def view(query, allargs):
if query == 'dlanalysis':
analysis_id = allargs.get('analysis_id')

if query == 'view_result':
return view_result(analysis_id)
elif query == 'basic_info':
return get_basic_info(analysis_id)
elif query == 'files':
return get_files_data(analysis_id)
elif query == 'urls':
return get_urls_data(analysis_id)
elif query == 'permissions':
return get_permissions_data(analysis_id)
elif query == 'domains':
return get_domains_data(analysis_id)
elif query == 'ips':
return get_ips_data(analysis_id)
elif query == 'emails':
return get_emails_data(analysis_id)
elif query == 'btc':
return get_btc_data(analysis_id)
elif query == 'comments':
return get_comments_data(analysis_id)
elif query == 'base64':
return get_base64_data(analysis_id)
elif query == 'manifest':
return get_manifest_data(analysis_id)
elif query == 'dlanalysis':
try:
extension_id = allargs.get('extid')
saveas = ""
Expand Down
Empty file added frontend/handlers/__init__.py
Empty file.
169 changes: 169 additions & 0 deletions frontend/handlers/data_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import os
import json
import base64
from flask import url_for
import core.core as core
import core.helper as helper
from .file_handler import load_analysis_data, get_analysis_files

def get_analysis_data(analysis_id):
"""Helper function to get common analysis data"""
analysis_info = core.get_result_info(analysis_id)
if not analysis_info[0]:
return None, None, None, None

result_directory = analysis_info[1]['report_directory'].replace('<reports_path>', core.reports_path)
files_valid, files = get_analysis_files(result_directory)
if not files_valid:
return None, None, None, None

graph_data, source_data, report_data = load_analysis_data(files)
return analysis_info, graph_data, source_data, report_data

def get_basic_info(analysis_id):
analysis_info, _, _, report_data = get_analysis_data(analysis_id)
if not analysis_info:
return {'error': 'Invalid analysis data'}

extension_type = report_data['type']
if 'firefox' in extension_type.lower():
extension_type = '<i class="fab fa-firefox"></i> ' + extension_type
elif 'chrome' in extension_type.lower():
extension_type = '<i class="fab fa-chrome"></i> ' + extension_type

return {
'name': report_data['name'],
'version': report_data['version'],
'author': report_data['author'],
'description': report_data['description'],
'time': analysis_info[1]['time'],
'extension_type': extension_type
}

def get_files_data(analysis_id):
_, _, source_data, report_data = get_analysis_data(analysis_id)
if not source_data:
return {'error': 'Invalid analysis data'}

files_table = '<table class="result-table" id="files-table"><thead><tr><th>File Name</th><th>Path</th><th>Size</th><th>Actions</th></tr></thead><tbody>'

for file_info in source_data:
file_name = source_data[file_info]['file_name']
rel_path = source_data[file_info]['relative_path']
file_id = source_data[file_info]['id']
file_size = source_data[file_info]['file_size']

file_action = f'<button class="bttn-fill bttn-xs bttn-primary" onclick="viewfile(\'{analysis_id}\', \'{file_id}\')"><i class="fas fa-code"></i> View Source</button>'

if file_name.endswith('.js') and source_data[file_id]['retirejs_result']:
file_action += f' <button class="bttn-fill bttn-xs bttn-danger" onclick="retirejsResult(\'{file_id}\', \'{analysis_id}\', \'{file_name}\')"><i class="fas fa-spider"></i> Vulnerabilities</button>'

file_type = helper.get_file_type_icon(file_name)
file_type = f'<img src="{file_type}" class="ft_icon">'

files_table += f"<tr><td>{file_type} {file_name}</td><td>{rel_path}</td><td>{file_size}</td><td>{file_action}</td></tr>"

files_table += '</tbody></table>'

return {
'files_table': files_table,
'counts': {
'js': len(report_data['files']['js']),
'css': len(report_data['files']['css']),
'html': len(report_data['files']['html']),
'json': len(report_data['files']['json']),
'other': len(report_data['files']['other']),
'static': len(report_data['files']['static'])
}
}

def get_urls_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

urls_table = '<table class="result-table" id="urls-table"><thead><tr><th>URL</th><th>Domain</th><th>File</th><th>Actions</th></tr></thead><tbody>'
for url in report_data.get('urls', []):
b64url = base64.b64encode(url['url'].encode()).decode()
urls_table += f'''
<tr>
<td><a href="{url['url']}" target="_blank">{url['url']}</a></td>
<td>{url['domain']}</td>
<td>{url['file']}</td>
<td>
<button class="bttn-fill bttn-xs bttn-primary" onclick="whois('{url['url']}')">WHOIS</button>
<button class="bttn-fill bttn-xs bttn-success" onclick="getSource('{b64url}')">Source</button>
<button class="bttn-fill bttn-xs bttn-danger" onclick="getHTTPHeaders('{b64url}')">Headers</button>
</td>
</tr>
'''
urls_table += '</tbody></table>'
return {'urls_table': urls_table}

def get_permissions_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

permissions = report_data.get('permissions', [])
return {'permissions': permissions}

def get_domains_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

domains = report_data.get('domains', [])
return {'domains': domains}

def get_ips_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

ips = report_data.get('ips', [])
return {'ips': ips}

def get_emails_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

emails = report_data.get('emails', [])
return {'emails': emails}

def get_btc_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

btc_addresses = report_data.get('btc_addresses', [])
return {'btc_addresses': btc_addresses}

def get_comments_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

comments = report_data.get('comments', [])
return {'comments': comments}

def get_base64_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

base64_strings = report_data.get('base64_strings', [])
return {'base64_strings': base64_strings}

def get_manifest_data(analysis_id):
_, _, _, report_data = get_analysis_data(analysis_id)
if not report_data:
return {'error': 'Invalid analysis data'}

manifest = report_data.get('manifest', {})
return {'manifest': manifest}

# Similar functions for other data types (urls, permissions, domains, etc.)
# Each function follows the same pattern of getting analysis data and returning
# formatted HTML tables or structured data
33 changes: 33 additions & 0 deletions frontend/handlers/file_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os
import json
import logging
import traceback

def validate_analysis_id(analysis_id):
try:
int(analysis_id.replace('EXA',''))
return True
except:
logging.error(traceback.format_exc())
return False

def get_analysis_files(result_directory):
graph_file = os.path.join(result_directory, 'graph.data')
report_json = os.path.join(result_directory, 'extanalysis_report.json')
source_file = os.path.join(result_directory, 'source.json')

return all([os.path.isfile(f) for f in [graph_file, report_json, source_file]]), {
'graph': graph_file,
'report': report_json,
'source': source_file
}

def load_analysis_data(files):
with open(files['graph'], 'r') as f:
graph_data = f.read()
with open(files['source'], 'r') as f:
source_data = json.loads(f.read())
with open(files['report'], 'r') as f:
report_data = json.loads(f.read())

return graph_data, source_data, report_data
Loading