Merge branch 'js-field'

main
Elbert Alias 8 years ago
commit 33c53bd639

@ -6,7 +6,7 @@ set -eu
echo "Validating apps.json..." echo "Validating apps.json..."
jsonlint-cli -s schema.json src/apps.json jsonlint-cli -tps schema.json src/apps.json > /tmp/apps.json && mv /tmp/apps.json src/apps.json
echo "Validating regular expressions..." echo "Validating regular expressions..."

@ -35,9 +35,9 @@
}, },
"required": true "required": true
}, },
"env": { "js": {
"type": [ "string", "array" ], "type": "object",
"items": { "additionalProperties": {
"type": "string" "type": "string"
} }
}, },

@ -38,6 +38,8 @@ class Driver {
this.wappalyzer.apps = json.apps; this.wappalyzer.apps = json.apps;
this.wappalyzer.categories = json.categories; this.wappalyzer.categories = json.categories;
this.wappalyzer.parseJsPatterns();
this.wappalyzer.driver.log = (message, source, type) => this.log(message, source, type); this.wappalyzer.driver.log = (message, source, type) => this.log(message, source, type);
this.wappalyzer.driver.displayApps = (detected, meta, context) => this.displayApps(detected, meta, context); this.wappalyzer.driver.displayApps = (detected, meta, context) => this.displayApps(detected, meta, context);
} }
@ -123,34 +125,15 @@ class Driver {
.finally(() => { .finally(() => {
this.timer('browser.wait end'); this.timer('browser.wait end');
const headers = {}; const headers = this.getHeaders(browser);
const html = this.getHtml(browser);
browser.resources['0'].response.headers._headers.forEach(header => { const scripts = this.getScripts(browser);
if ( !headers[header[0]] ){ const js = this.getJs(browser);
headers[header[0]] = [];
}
headers[header[0]].push(header[1]);
});
let html = '';
try {
html = browser.html();
} catch ( e ) {
this.wappalyzer.log(error.message, 'browser', 'error');
}
const vars = Object.getOwnPropertyNames(browser.window);
const scripts = Array.prototype.slice
.apply(browser.document.scripts)
.filter(s => s.src)
.map(s => s.src);
this.wappalyzer.analyze(pageUrl.hostname, pageUrl.href, { this.wappalyzer.analyze(pageUrl.hostname, pageUrl.href, {
headers, headers,
html, html,
env: vars, js,
scripts scripts
}); });
@ -163,6 +146,70 @@ class Driver {
}); });
} }
getHeaders(browser) {
const headers = {};
browser.resources['0'].response.headers._headers.forEach(header => {
if ( !headers[header[0]] ){
headers[header[0]] = [];
}
headers[header[0]].push(header[1]);
});
return headers;
}
getHtml(browser) {
let html = '';
try {
html = browser.html();
} catch ( e ) {
this.wappalyzer.log(error.message, 'browser', 'error');
}
return html;
}
getScripts(browser) {
const scripts = Array.prototype.slice
.apply(browser.document.scripts)
.filter(s => s.src)
.map(s => s.src);
return scripts;
}
getJs(browser) {
const patterns = this.wappalyzer.jsPatterns;
const js = {};
Object.keys(patterns).forEach(appName => {
js[appName] = {};
Object.keys(patterns[appName]).forEach(chain => {
js[appName][chain] = {};
patterns[appName][chain].forEach((pattern, index) => {
const properties = chain.split('.');
let value = properties.reduce((parent, property) => {
return parent && parent.hasOwnProperty(property) ? parent[property] : null;
}, browser.window);
value = typeof value === 'string' ? value : !!value;
if ( value ) {
js[appName][chain][index] = value;
}
});
});
});
return js;
}
crawl(pageUrl, index = 1, depth = 1) { crawl(pageUrl, index = 1, depth = 1) {
this.timer('crawl'); this.timer('crawl');

@ -1,58 +1,58 @@
/** global: browser */ /** global: browser */
if ( typeof browser !== 'undefined' && typeof document.body !== 'undefined' ) { if ( typeof browser !== 'undefined' && typeof document.body !== 'undefined' ) {
var html = document.documentElement.outerHTML; try {
var html = document.documentElement.outerHTML;
if ( html.length > 50000 ) { if ( html.length > 50000 ) {
html = html.substring(0, 25000) + html.substring(html.length - 25000, html.length); html = html.substring(0, 25000) + html.substring(html.length - 25000, html.length);
} }
var scripts = Array.prototype.slice const scripts = Array.prototype.slice
.apply(document.scripts) .apply(document.scripts)
.filter(s => s.src) .filter(script => script.src)
.map(s => s.src); .map(script => script.src);
try {
browser.runtime.sendMessage({
id: 'analyze',
subject: { html },
source: 'content.js'
});
browser.runtime.sendMessage({ browser.runtime.sendMessage({
id: 'analyze', id: 'analyze',
subject: { scripts }, subject: { html, scripts },
source: 'content.js' source: 'content.js'
}); });
var container = document.createElement('wappalyzerData'); const script = document.createElement('script');
container.setAttribute('id', 'wappalyzerData'); script.onload = () => {
container.setAttribute('style', 'display: none'); addEventListener('message', event => {
if ( event.data.id !== 'js' ) {
return;
}
var script = document.createElement('script'); browser.runtime.sendMessage({
id: 'analyze',
subject: {
js: event.data.js
},
source: 'content.js'
});
}, true);
script.setAttribute('id', 'wappalyzerEnvDetection'); ( chrome || browser ).runtime.sendMessage({
script.setAttribute('src', browser.extension.getURL('js/inject.js')); id: 'init_js',
subject: {},
container.addEventListener('wappalyzerEvent', (event => {
var env = event.target.childNodes[0].nodeValue;
document.documentElement.removeChild(container);
document.documentElement.removeChild(script);
env = env.split(' ').slice(0, 500);
browser.runtime.sendMessage({
id: 'analyze',
subject: { env },
source: 'content.js' source: 'content.js'
}, response => {
postMessage({
id: 'patterns',
patterns: response.patterns
}, '*');
}); });
}), true); };
script.setAttribute('id', 'wappalyzer');
script.setAttribute('src', browser.extension.getURL('js/inject.js'));
document.documentElement.appendChild(container); document.body.appendChild(script);
document.documentElement.appendChild(script); } catch (e) {
} catch(e) {
log(e); log(e);
} }
} }

@ -75,6 +75,8 @@ fetch('../apps.json')
wappalyzer.apps = json.apps; wappalyzer.apps = json.apps;
wappalyzer.categories = json.categories; wappalyzer.categories = json.categories;
wappalyzer.parseJsPatterns();
categoryOrder = Object.keys(wappalyzer.categories).sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority); categoryOrder = Object.keys(wappalyzer.categories).sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority);
}) })
.catch(error => { .catch(error => {
@ -191,6 +193,12 @@ browser.webRequest.onCompleted.addListener(request => {
categories: wappalyzer.categories categories: wappalyzer.categories
}; };
break;
case 'init_js':
response = {
patterns: wappalyzer.jsPatterns
};
break; break;
default: default:
} }

@ -1,16 +1,43 @@
(function() { (function() {
try { try {
var i, environmentVars = '', e = document.createEvent('Events'); addEventListener('message', (event => {
if ( event.data.id !== 'patterns' ) {
return;
}
e.initEvent('wappalyzerEvent', true, false); const patterns = event.data.patterns || {};
for ( i in window ) { const js = {};
environmentVars += i + ' ';
}
document.getElementById('wappalyzerData').appendChild(document.createComment(environmentVars)); Object.keys(patterns).forEach(appName => {
document.getElementById('wappalyzerData').dispatchEvent(e); js[appName] = {};
} catch(e) {
// Fail quietly Object.keys(patterns[appName]).forEach(chain => {
} js[appName][chain] = {};
patterns[appName][chain].forEach((pattern, index) => {
const value = detectJs(chain);
if ( value ) {
js[appName][chain][index] = value;
}
});
});
});
postMessage({ id: 'js', js }, '*');
}), false);
} catch(e) {
// Fail quietly
}
}()); }());
function detectJs(chain) {
const properties = chain.split('.');
const value = properties.reduce((parent, property) => {
return parent && parent.hasOwnProperty(property) ? parent[property] : null;
}, window);
return typeof value === 'string' ? value : !!value;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

@ -18,6 +18,7 @@ class Wappalyzer {
this.apps = {}; this.apps = {};
this.categories = {}; this.categories = {};
this.driver = {}; this.driver = {};
this.jsPatterns = {};
this.detected = {}; this.detected = {};
this.hostnameCache = {}; this.hostnameCache = {};
@ -87,6 +88,12 @@ class Wappalyzer {
} }
}) })
if ( data.js ) {
Object.keys(data.js).forEach(appName => {
this.analyzeJs(apps[appName], data.js[appName]);
});
}
Object.keys(apps).forEach(appName => { Object.keys(apps).forEach(appName => {
var app = apps[appName]; var app = apps[appName];
@ -249,6 +256,17 @@ class Wappalyzer {
return parsed; return parsed;
} }
/**
* Parse JavaScript patterns
*/
parseJsPatterns() {
Object.keys(this.apps).forEach(appName => {
if ( this.apps[appName].js ) {
this.jsPatterns[appName] = this.parsePatterns(this.apps[appName].js);
}
});
}
resolveExcludes(apps) { resolveExcludes(apps) {
var excludes = []; var excludes = [];
@ -487,6 +505,25 @@ class Wappalyzer {
} }
} }
/**
* Analyze JavaScript variables
*/
analyzeJs(app, results) {
console.log(app, results);
Object.keys(results).forEach(string => {
Object.keys(results[string]).forEach(index => {
const pattern = this.jsPatterns[app.name][string][index];
const value = results[string][index];
if ( pattern.regex.test(value) ) {
this.addDetected(app, pattern, 'js', value);
}
});
});
}
/** /**
* Analyze robots.txt * Analyze robots.txt
*/ */