Introducción
Webpack es un módulo de empaquetado para aplicaciones JavaScript modernas. Su principal función es tomar módulos con dependencias y generar activos estáticos que representen esos módulos. Webpack puede manejar una variedad de archivos y convertirlos en un solo archivo o en varios archivos que son más eficientes para servir en una aplicación web.
Los archivos de mapas de origen (source maps) son archivos que mapean el código comprimido o transformado (como el que se produce después de la compilación y minificación con Webpack) a su código fuente original. Estos archivos son extremadamente útiles para la depuración, ya que permiten a los desarrolladores ver y trabajar con el código original en el navegador, incluso si el código que realmente se está ejecutando ha sido transformado.
Al analizar el código fuente del frontal de una aplicación web comprimida con Webpack es complicado realizar el proceso de ingeniería inversa, ya que el código resultante es extremadamente diferente al código original. En algunos casos, debido a una mala configuración de Webpack, se generan los archivos de mapas de origen y se suben al servidor de producción de la aplicación web, resultando en la recuperación completa del código fuente del frontal web. Se ha creado una aplicación en Python que, a partir de un enlace, explora la página web en busca de código fuente JavaScript y comprueba si se puede descargar su correspondiente archivo .map. Si es posible, lo descarga y extrae el código fuente original.
Uso de UnWebpack
La ejecución de la aplicación Python requiere dos argumentos, en primer lugar la dirección del sitio web a escanear y en segundo lugar el directorio local en el que queremos que se guarden los archivos. Este es un ejemplo de su ejecución:
python .\unwebpack.py 'https://webpage.com' 'C:\\files\\'
Código fuente
from bs4 import BeautifulSoup
import json
import os
import re
import requests
import sys
def get_request(url):
return requests.get(url, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0'
})
def get_source_code_urls(html_code):
bs = BeautifulSoup(html_code, 'lxml')
elements = []
elements.extend(bs.find_all('link'))
elements.extend(bs.find_all('script'))
links = []
for elem in elements:
url = ''
if 'href' in elem.attrs:
url = elem.get('href')
elif 'src' in elem.attrs:
url = elem.get('src')
if url != '':
# Ignore no relative resources
if 'https://' in url or 'http://' in url:
continue
# Take the domain root and the resource
url = '/'.join(WEB_URL.split('/')[:3]) + url
if url[-3:] == '.js':
if url not in links:
links.append(url)
return links
def get_source_code_map_urls(source_urls):
links = []
for source_url in source_urls:
javascript_request = get_request(source_url)
# search for sourceMappingURL
found = re.search('//# sourceMappingURL=(.*).js.map', javascript_request.text)
if found:
links.append(source_url + '.map')
else:
print(source_url + ' has no map file')
return links
def get_source_code_maps(map_urls):
maps = []
for map_url in map_urls:
map_request = get_request(map_url)
try:
map_json = json.loads(map_request.text)
except:
print(map_url + ' error loading map file')
continue
maps.append(map_json)
return maps
def get_path_level(path):
# ./ path
if re.match('^\./.*', path):
return 'level_1'
# ../../ ... path
elif re.match('^\.\./.*', path):
findings = re.findall('\.\./', path)
return 'level_' + str(len(findings))
# (webpack) path
elif re.match('^\(webpack\).*', path):
return 'webpack'
# other paths
else:
return 'level_1'
def save_map_files(map):
it = 0
for path in map['sources']:
# Remove webpack:/// from path
path = path.split('webpack:///')[1]
# Get the level of the path (subdirectories)
path_level = get_path_level(path)
# Create the path of the file and remove the subdirectories part
file_path = DOWNLOAD_PATH + path_level + ('\\' if os.name == 'nt' else '/')
file_path += re.sub('(\.|\.\.|\(webpack\))/', '', path)
# Remove sync invalid characters
file_path = file_path.replace('^\.\\\\.*$', '')
# Replace invalid characters with "-" character
file_path = re.sub('[*|?|<|"|>|\|]', '-', file_path)
# Change the path for Windows
file_path = file_path.replace('/', '\\') if os.name == 'nt' else file_path
# Create the parent directory
file_directory = '\\'.join(file_path.split('\\')[:-1])
os.makedirs(file_directory, exist_ok=True)
# Write the source code file
file = open(file_path, 'w', encoding='utf-8')
file.write(map['sourcesContent'][it])
file.close()
it += 1
def process_webpack_application(web_url):
html_request = get_request(web_url)
source_code_urls = get_source_code_urls(html_request.text)
source_code_map_urls = get_source_code_map_urls(source_code_urls)
source_code_maps = get_source_code_maps(source_code_map_urls)
for map in source_code_maps:
save_map_files(map)
WEB_URL = sys.argv[1]
DOWNLOAD_PATH = sys.argv[2]
process_webpack_application(WEB_URL)