Skip to content
This repository has been archived by the owner on Dec 3, 2019. It is now read-only.

Commit

Permalink
Overwrite the 'Content-Security-Policy' header in WPR replay.
Browse files Browse the repository at this point in the history
Some html files have a 'Content-Security-Policy' header that blocks
execution of JavaScript from untrusted sources, including inline
JavaScripts. WPR injects JavaScript snippets as inline JavaScript inside
html files. So a restrictive 'Content-Security-Policy' can block a page
from executing WPR injected script.

The fix is tokenize a file's 'Content-Security-Policy' header, remove
any tokens that prevents inline JavaScript execution, and add a
'unsafe-inline' token that opens the permission for a page to execute
inline scripts.

The Autofill Captured Sites Automation Framework requires this fix, as
the framework relies on injected scripts to drive automation on captured
sites.

Bug: catapult:#1234
Chromium Bug: 850768
Change-Id: I9058c0722c14d1b5ea817b0db22ae603f464fde2
Reviewed-on: https://chromium-review.googlesource.com/c/1220886
Reviewed-by: Ned Nguyen <nednguyen@google.com>
Commit-Queue: Yiming Zhou <uwyiming@google.com>
  • Loading branch information
Yiming Zhou authored and Commit Bot committed Oct 4, 2018
1 parent e3b37a4 commit 1b6e81b
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 60 deletions.
26 changes: 13 additions & 13 deletions telemetry/telemetry/internal/binary_dependencies.json
Original file line number Diff line number Diff line change
Expand Up @@ -330,39 +330,39 @@
"cloud_storage_bucket": "chromium-telemetry",
"file_info": {
"linux_aarch64": {
"cloud_storage_hash": "357974eb080c44e2f45176efced06777387f1620",
"cloud_storage_hash": "11358915ee6f1c38c3564d57f1a6852a3caf22e4",
"download_path": "bin/linux/aarch64/wpr",
"version_in_cs": "8d87482f68b6438fba08178c23be73ccd978da73"
"version_in_cs": "a80b458e2a81c7eb3b1dcee88a38b1ad36040e76"
},
"linux_armv7l": {
"cloud_storage_hash": "71172a7d7a39a511edbbbd5ace9acb68ab50ca0c",
"cloud_storage_hash": "50d5b3ca0935b3f15ef7da50dc9de9ce42767c33",
"download_path": "bin/linux/armv7l/wpr",
"version_in_cs": "8d87482f68b6438fba08178c23be73ccd978da73"
},
"linux_mips": {
"cloud_storage_hash": "bf70294611513d2697415d05908ba9e31935310b",
"cloud_storage_hash": "f4a94ab68021a061190ab91a9c13941bcccce1f0",
"download_path": "bin/linux/mips/wpr",
"version_in_cs": "8d87482f68b6438fba08178c23be73ccd978da73"
"version_in_cs": "a80b458e2a81c7eb3b1dcee88a38b1ad36040e76"
},
"linux_x86_64": {
"cloud_storage_hash": "907a05a59df57301c038c0c9c9bcd622405a18c7",
"cloud_storage_hash": "97620e59f3acc2e7f0d926a2e47141bc4001a636",
"download_path": "bin/linux/x86_64/wpr",
"version_in_cs": "8d87482f68b6438fba08178c23be73ccd978da73"
"version_in_cs": "a80b458e2a81c7eb3b1dcee88a38b1ad36040e76"
},
"mac_x86_64": {
"cloud_storage_hash": "e5a7deaa4c52967f746315475c1af979f580d481",
"cloud_storage_hash": "af19a79485eab73f909eed8309783a357b87e097",
"download_path": "bin/mac/x86_64/wpr",
"version_in_cs": "8d87482f68b6438fba08178c23be73ccd978da73"
"version_in_cs": "a80b458e2a81c7eb3b1dcee88a38b1ad36040e76"
},
"win_AMD64": {
"cloud_storage_hash": "1d774b61c041fe6b33133e5b1cad27352bf21359",
"cloud_storage_hash": "9dee47867a6691d2a5343ea0fae533b6c3c5aaef",
"download_path": "bin/win/AMD64/wpr.exe",
"version_in_cs": "8d87482f68b6438fba08178c23be73ccd978da73"
"version_in_cs": "a80b458e2a81c7eb3b1dcee88a38b1ad36040e76"
},
"win_x86": {
"cloud_storage_hash": "ce7cf20220595b8b72131d4bb83c7f26e9cac6df",
"cloud_storage_hash": "a24f5c5782d20c80f3974d432814a66b62ac74ea",
"download_path": "bin/win/x86/wpr.exe",
"version_in_cs": "8d87482f68b6438fba08178c23be73ccd978da73"
"version_in_cs": "a80b458e2a81c7eb3b1dcee88a38b1ad36040e76"
}
}
}
Expand Down
87 changes: 67 additions & 20 deletions web_page_replay_go/src/webpagereplay/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"os"
"strconv"
"strings"
"time"
"time"
)

const errStatus = http.StatusInternalServerError
Expand All @@ -38,27 +38,71 @@ func fixupRequestURL(req *http.Request, scheme string) {

// updateDate is the basic function for date adjustment.
func updateDate(h http.Header, name string, now, oldNow time.Time) {
val := h.Get(name)
if val == "" {
return
}
oldTime, err := http.ParseTime(val)
if err != nil {
return
}
newTime := now.Add(oldTime.Sub(oldNow))
h.Set(name, newTime.UTC().Format(http.TimeFormat))
val := h.Get(name)
if val == "" {
return
}
oldTime, err := http.ParseTime(val)
if err != nil {
return
}
newTime := now.Add(oldTime.Sub(oldNow))
h.Set(name, newTime.UTC().Format(http.TimeFormat))
}

// updateDates updates "Date" header as current time and adjusts "Last-Modified"/"Expires" against it.
func updateDates(h http.Header, now time.Time) {
oldNow, err := http.ParseTime(h.Get("Date"))
h.Set("Date", now.UTC().Format(http.TimeFormat))
if err != nil {
return
}
updateDate(h, "Last-Modified", now, oldNow)
updateDate(h, "Expires", now, oldNow)
oldNow, err := http.ParseTime(h.Get("Date"))
h.Set("Date", now.UTC().Format(http.TimeFormat))
if err != nil {
return
}
updateDate(h, "Last-Modified", now, oldNow)
updateDate(h, "Expires", now, oldNow)
}

// updateContentSecurityPolicy add "unsafe-inline" to the "Content-Security-Policy" header. This
// allows a page under replay to execute the scripts WPR injects.
func updateContentSecurityPolicy(h http.Header) {
oldContentSecurityPolicy := h.Get("Content-Security-Policy")
if oldContentSecurityPolicy == "" {
return
}

tokens := strings.Split(oldContentSecurityPolicy, ";")
for i := 0; i < len(tokens); i++ {
token := strings.Trim(tokens[i], " ")

// If a request contains the script-src token but not the 'unsafe-inline' rule,
// add the rule to the script-src.
if strings.HasPrefix(token, "script-src ") {
// Break 'script src' into more tokens, and remove any nounce or hashes that will
// invalidate the 'unsafe-inline' rule.
scriptSrcTokensList := strings.Split(token, " ")
newScriptSrcToken := "script-src 'unsafe-inline' "

for j := 1; j < len(scriptSrcTokensList); j++ {
scriptSrcToken := strings.Trim(scriptSrcTokensList[j], " ")
if !strings.HasPrefix(scriptSrcToken, "'nonce-") &&
!strings.HasPrefix(scriptSrcToken, "'sha256-") &&
!strings.HasPrefix(scriptSrcToken, "'sha384-") &&
!strings.HasPrefix(scriptSrcToken, "'sha512-") &&
// The 'strict-dynamic' token invalidates the 'unsafe-inline' rule.
// So this function will remove the 'strict-dynamic' token from the
// 'Content-Security-Policy' header.
scriptSrcToken != "'strict-dynamic'" &&
scriptSrcToken != "'unsafe-inline'" {
newScriptSrcToken += scriptSrcToken + " "
}
}

tokens[i] = newScriptSrcToken
break
}
}

newContentSecurityPolicy := strings.Join(tokens, "; ")
h.Set("Content-Security-Policy", newContentSecurityPolicy)
}

// NewReplayingProxy constructs an HTTP proxy that replays responses from an archive.
Expand Down Expand Up @@ -128,8 +172,11 @@ func (proxy *replayingProxy) ServeHTTP(w http.ResponseWriter, req *http.Request)
}
}

// Update dates in response header.
updateDates(storedResp.Header, time.Now())
// Update dates in response header.
updateDates(storedResp.Header, time.Now())

// Allow the page to execute inline scripts.
updateContentSecurityPolicy(storedResp.Header)

// Transform.
for _, t := range proxy.transformers {
Expand Down
101 changes: 74 additions & 27 deletions web_page_replay_go/src/webpagereplay/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,33 +242,80 @@ func TestEndToEnd(t *testing.T) {
}

func TestUpdateDates(t *testing.T) {
const (
oldDate = "Thu, 17 Aug 2017 12:00:00 GMT"
oldLastModified = "Thu, 17 Aug 2017 09:00:00 GMT"
oldExpires = "Thu, 17 Aug 2017 17:00:00 GMT"
newDate = "Fri, 17 Aug 2018 12:00:00 GMT"
newLastModified = "Fri, 17 Aug 2018 09:00:00 GMT"
newExpires = "Fri, 17 Aug 2018 17:00:00 GMT"
)
now, err := http.ParseTime(newDate)
if err != nil {
t.Fatal(err)
}
const (
oldDate = "Thu, 17 Aug 2017 12:00:00 GMT"
oldLastModified = "Thu, 17 Aug 2017 09:00:00 GMT"
oldExpires = "Thu, 17 Aug 2017 17:00:00 GMT"
newDate = "Fri, 17 Aug 2018 12:00:00 GMT"
newLastModified = "Fri, 17 Aug 2018 09:00:00 GMT"
newExpires = "Fri, 17 Aug 2018 17:00:00 GMT"
)
now, err := http.ParseTime(newDate)
if err != nil {
t.Fatal(err)
}

responseHeader := http.Header{
"Date": {oldDate},
"Last-Modified": {oldLastModified},
"Expires": {oldExpires},
}
updateDates(responseHeader, now)

wantHeader := http.Header{
"Date": {newDate},
"Last-Modified": {newLastModified},
"Expires": {newExpires},
}
// Check if dates are updated as expected.
if !reflect.DeepEqual(responseHeader, wantHeader) {
t.Errorf("got: %v\nwant: %v\n", responseHeader, wantHeader)
}
}

responseHeader := http.Header{
"Date": {oldDate},
"Last-Modified": {oldLastModified},
"Expires": {oldExpires},
}
updateDates(responseHeader, now)
func assertEquals(t *testing.T, actual, expected string) {
if expected != actual {
t.Errorf("Expected \"%s\" but was \"%s\"", expected, actual)
}
}

func UpdateContentSecurityPolicyForInlineScript(
oldContentSecurityPolicy string) string {
responseHeader := http.Header{
"Content-Security-Policy": {oldContentSecurityPolicy},
}
updateContentSecurityPolicy(responseHeader)
contentSecurityPolicy := responseHeader.Get("Content-Security-Policy")
return contentSecurityPolicy
}

wantHeader := http.Header{
"Date": {newDate},
"Last-Modified": {newLastModified},
"Expires": {newExpires},
}
// Check if dates are updated as expected.
if !reflect.DeepEqual(responseHeader, wantHeader) {
t.Errorf("got: %v\nwant: %v\n", responseHeader, wantHeader)
}
func TestContentSecurityPolicy(t *testing.T) {
assertEquals(t,
UpdateContentSecurityPolicyForInlineScript(
"script-src 'self' https://foo.com;"),
"script-src 'unsafe-inline' 'self' https://foo.com ; ")
assertEquals(t,
UpdateContentSecurityPolicyForInlineScript(
"script-src 'strict-dynamic' 'nonce-2726c7f26c'"),
"script-src 'unsafe-inline' ")
assertEquals(t,
UpdateContentSecurityPolicyForInlineScript(
"script-src 'sha256-pwltXkdHyMvChFSLNauyy5WItOFOm+iDDsgqRTr8peI='"),
"script-src 'unsafe-inline' ")
assertEquals(t,
UpdateContentSecurityPolicyForInlineScript(
"script-src "+
"'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/"+
"uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC'"),
"script-src 'unsafe-inline' ")
assertEquals(t,
UpdateContentSecurityPolicyForInlineScript(
"script-src 'sha512-cLuU6nVzrYJlo7rUa6TMmz3nylPFrPQrEUpOHllb5ic='"),
"script-src 'unsafe-inline' ")
assertEquals(t,
UpdateContentSecurityPolicyForInlineScript(
"script-src 'self' https://foo.com "+
"'sha512-cLuU6nVzrYJlo7rUa6TMmz3nylPFrPQrEUpOHllb5ic=' "+
"'strict-dynamic' 'nonce-2726c7f26c';"),
"script-src 'unsafe-inline' 'self' https://foo.com ; ")
}

0 comments on commit 1b6e81b

Please sign in to comment.