forked from mirrors/gecko-dev
Bug 1527280 - Add deterministic js and injection script to raptor mitmproxy r=davehunt
Differential Revision: https://phabricator.services.mozilla.com/D19485 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
c107146bac
commit
8367e1b8cd
4 changed files with 268 additions and 0 deletions
|
|
@ -329,6 +329,8 @@ testing/talos/talos/tests/v8_7/**
|
||||||
testing/talos/talos/tests/kraken/**
|
testing/talos/talos/tests/kraken/**
|
||||||
# Runing Talos may extract data here, see bug 1435677.
|
# Runing Talos may extract data here, see bug 1435677.
|
||||||
testing/talos/talos/tests/tp5n/**
|
testing/talos/talos/tests/tp5n/**
|
||||||
|
# Raptor third party
|
||||||
|
testing/raptor/raptor/playback/scripts/catapult/**
|
||||||
|
|
||||||
testing/web-platform/**
|
testing/web-platform/**
|
||||||
testing/xpcshell/moz-http2/**
|
testing/xpcshell/moz-http2/**
|
||||||
|
|
|
||||||
27
testing/raptor/raptor/playback/scripts/catapult/LICENSE
Normal file
27
testing/raptor/raptor/playback/scripts/catapult/LICENSE
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright 2015 The Chromium Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of catapult nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var random_count = 0;
|
||||||
|
var random_count_threshold = 25;
|
||||||
|
var random_seed = 0.462;
|
||||||
|
Math.random = function() {
|
||||||
|
random_count++;
|
||||||
|
if (random_count > random_count_threshold){
|
||||||
|
random_seed += 0.1;
|
||||||
|
random_count = 1;
|
||||||
|
}
|
||||||
|
return (random_seed % 1);
|
||||||
|
};
|
||||||
|
if (typeof(crypto) == 'object' &&
|
||||||
|
typeof(crypto.getRandomValues) == 'function') {
|
||||||
|
crypto.getRandomValues = function(arr) {
|
||||||
|
var scale = Math.pow(256, arr.BYTES_PER_ELEMENT);
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
arr[i] = Math.floor(Math.random() * scale);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
(function () {
|
||||||
|
var date_count = 0;
|
||||||
|
var date_count_threshold = 25;
|
||||||
|
var orig_date = Date;
|
||||||
|
// Time since epoch in milliseconds. This is replaced by script injector with
|
||||||
|
// the date when the recording is done.
|
||||||
|
var time_seed = REPLACE_LOAD_TIMESTAMP;
|
||||||
|
Date = function() {
|
||||||
|
if (this instanceof Date) {
|
||||||
|
date_count++;
|
||||||
|
if (date_count > date_count_threshold){
|
||||||
|
time_seed += 50;
|
||||||
|
date_count = 1;
|
||||||
|
}
|
||||||
|
switch (arguments.length) {
|
||||||
|
case 0: return new orig_date(time_seed);
|
||||||
|
case 1: return new orig_date(arguments[0]);
|
||||||
|
default: return new orig_date(arguments[0], arguments[1],
|
||||||
|
arguments.length >= 3 ? arguments[2] : 1,
|
||||||
|
arguments.length >= 4 ? arguments[3] : 0,
|
||||||
|
arguments.length >= 5 ? arguments[4] : 0,
|
||||||
|
arguments.length >= 6 ? arguments[5] : 0,
|
||||||
|
arguments.length >= 7 ? arguments[6] : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Date().toString();
|
||||||
|
};
|
||||||
|
Date.__proto__ = orig_date;
|
||||||
|
Date.prototype = orig_date.prototype;
|
||||||
|
Date.prototype.constructor = Date;
|
||||||
|
orig_date.now = function() {
|
||||||
|
return new Date().getTime();
|
||||||
|
};
|
||||||
|
orig_date.prototype.getTimezoneOffset = function() {
|
||||||
|
var dst2010Start = 1268560800000;
|
||||||
|
var dst2010End = 1289120400000;
|
||||||
|
if (this.getTime() >= dst2010Start && this.getTime() < dst2010End)
|
||||||
|
return 420;
|
||||||
|
return 480;
|
||||||
|
};
|
||||||
|
})();
|
||||||
168
testing/raptor/raptor/playback/scripts/inject-deterministic.py
Normal file
168
testing/raptor/raptor/playback/scripts/inject-deterministic.py
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from mitmproxy import ctx
|
||||||
|
|
||||||
|
|
||||||
|
class AddDeterministic():
|
||||||
|
|
||||||
|
def get_csp_directives(self, headers):
|
||||||
|
csp = headers.get("Content-Security-Policy", "")
|
||||||
|
return [d.strip() for d in csp.split(";")]
|
||||||
|
|
||||||
|
def get_csp_script_sources(self, headers):
|
||||||
|
sources = []
|
||||||
|
for directive in self.get_csp_directives(headers):
|
||||||
|
if directive.startswith("script-src "):
|
||||||
|
sources = directive.split()[1:]
|
||||||
|
return sources
|
||||||
|
|
||||||
|
def get_nonce_from_headers(self, headers):
|
||||||
|
"""
|
||||||
|
get_nonce_from_headers returns the nonce token from a
|
||||||
|
Content-Security-Policy (CSP) header's script source directive.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
For more background information on CSP and nonce, please refer to
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/
|
||||||
|
Content-Security-Policy/script-src
|
||||||
|
https://developers.google.com/web/fundamentals/security/csp/
|
||||||
|
"""
|
||||||
|
|
||||||
|
for source in self.get_csp_script_sources(headers) or []:
|
||||||
|
if source.startswith("'nonce-"):
|
||||||
|
return source.partition("'nonce-")[-1][:-1]
|
||||||
|
|
||||||
|
def get_script_with_nonce(self, script, nonce=None):
|
||||||
|
"""
|
||||||
|
Given a nonce, get_script_with_nonce returns the injected script text with the nonce.
|
||||||
|
|
||||||
|
If nonce None, get_script_with_nonce returns the script block
|
||||||
|
without attaching a nonce attribute.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Some responses may specify a nonce inside their Content-Security-Policy,
|
||||||
|
script-src directive.
|
||||||
|
The script injector needs to set the injected script's nonce attribute to
|
||||||
|
open execute permission for the injected script.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if nonce:
|
||||||
|
return '<script nonce="{}">{}</script>'.format(nonce, script)
|
||||||
|
return '<script>{}</script>'.format(script)
|
||||||
|
|
||||||
|
def update_csp_script_src(self, headers, sha256):
|
||||||
|
"""
|
||||||
|
Update the CSP script directives with appropriate information
|
||||||
|
|
||||||
|
Without this permissions a page with a
|
||||||
|
restrictive CSP will not execute injected scripts.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
For more background information on CSP, please refer to
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/
|
||||||
|
Content-Security-Policy/script-src
|
||||||
|
https://developers.google.com/web/fundamentals/security/csp/
|
||||||
|
"""
|
||||||
|
|
||||||
|
sources = self.get_csp_script_sources(headers)
|
||||||
|
add_unsafe = True
|
||||||
|
|
||||||
|
for token in sources:
|
||||||
|
if token == "'unsafe-inline'":
|
||||||
|
add_unsafe = False
|
||||||
|
ctx.log.info("Contains unsafe-inline")
|
||||||
|
elif token.startswith("'sha"):
|
||||||
|
sources.append("'sha256-{}'".format(sha256))
|
||||||
|
add_unsafe = False
|
||||||
|
ctx.log.info("Add sha hash directive")
|
||||||
|
break
|
||||||
|
|
||||||
|
if add_unsafe:
|
||||||
|
ctx.log.info("Add unsafe")
|
||||||
|
sources.append("'unsafe-inline'")
|
||||||
|
|
||||||
|
return "script-src {}".format(" ".join(sources))
|
||||||
|
|
||||||
|
def get_new_csp_header(self, headers, updated_csp_script):
|
||||||
|
"""
|
||||||
|
get_new_csp_header generates a new header object containing
|
||||||
|
the updated elements from new_csp_script_directives
|
||||||
|
"""
|
||||||
|
|
||||||
|
if updated_csp_script:
|
||||||
|
directives = self.get_csp_directives(headers)
|
||||||
|
for index, directive in enumerate(directives):
|
||||||
|
if directive.startswith("script-src "):
|
||||||
|
directives[index] = updated_csp_script
|
||||||
|
|
||||||
|
ctx.log.info("Original Header %s \n" % headers["Content-Security-Policy"])
|
||||||
|
headers["Content-Security-Policy"] = "; ".join(directives)
|
||||||
|
ctx.log.info("Updated Header %s \n" % headers["Content-Security-Policy"])
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def response(self, flow):
|
||||||
|
|
||||||
|
millis = int(round(time.time() * 1000))
|
||||||
|
|
||||||
|
if "content-type" in flow.response.headers:
|
||||||
|
if 'text/html' in flow.response.headers["content-type"]:
|
||||||
|
ctx.log.info("Working on {}".format(flow.response.headers["content-type"]))
|
||||||
|
|
||||||
|
flow.response.decode()
|
||||||
|
html = flow.response.text
|
||||||
|
|
||||||
|
with open("scripts/catapult/deterministic.js", "r") as jsfile:
|
||||||
|
js = jsfile.read().replace("REPLACE_LOAD_TIMESTAMP", str(millis))
|
||||||
|
|
||||||
|
if js not in html:
|
||||||
|
script_index = re.search('(?i).*?<head.*?>', html)
|
||||||
|
if script_index is None:
|
||||||
|
script_index = re.search('(?i).*?<html.*?>', html)
|
||||||
|
if script_index is None:
|
||||||
|
script_index = re.search('(?i).*?<!doctype html>', html)
|
||||||
|
if script_index is None:
|
||||||
|
ctx.log.info("No start tags found in request {}. Skip injecting".
|
||||||
|
format(flow.request.url))
|
||||||
|
return
|
||||||
|
script_index = script_index.end()
|
||||||
|
|
||||||
|
nonce = None
|
||||||
|
|
||||||
|
if flow.response.headers.get("Content-Security-Policy", False):
|
||||||
|
nonce = self.get_nonce_from_headers(flow.response.headers)
|
||||||
|
ctx.log.info("nonce : %s" % nonce)
|
||||||
|
|
||||||
|
if self.get_csp_script_sources(flow.response.headers) and not nonce:
|
||||||
|
# generate sha256 for the script
|
||||||
|
hash_object = hashlib.sha256(js.encode('utf-8'))
|
||||||
|
script_sha256 = base64.b64encode(hash_object.digest()). \
|
||||||
|
decode("utf-8")
|
||||||
|
|
||||||
|
# generate the new response headers
|
||||||
|
updated_script_sources = self.update_csp_script_src(
|
||||||
|
flow.response.headers,
|
||||||
|
script_sha256)
|
||||||
|
flow.response.headers = self.get_new_csp_header(
|
||||||
|
flow.response.headers,
|
||||||
|
updated_script_sources)
|
||||||
|
|
||||||
|
# generate new html file
|
||||||
|
new_html = html[:script_index] + \
|
||||||
|
self.get_script_with_nonce(js, nonce) + \
|
||||||
|
html[script_index:]
|
||||||
|
flow.response.text = new_html
|
||||||
|
|
||||||
|
ctx.log.info("In request {} injected deterministic JS".
|
||||||
|
format(flow.request.url))
|
||||||
|
else:
|
||||||
|
ctx.log.info("Script already injected in request {}".
|
||||||
|
format(flow.request.url))
|
||||||
|
|
||||||
|
|
||||||
|
def start():
|
||||||
|
ctx.log.info("Load Deterministic JS")
|
||||||
|
return AddDeterministic()
|
||||||
Loading…
Reference in a new issue