profile
viewpoint
Roberto Clapis empijei Google Switzerland https://empijei.science Go coder and information security engineer with an horrible sense of humor. Currently SecEng @Google, trying to make the world more secure and colorful.

empijei/dotfiles 5

My rc files

empijei/CodeAnalysis 2

A collection of tools and scripts to analyse code

empijei/ColorFix 2

An in-development filter to allow deuteranomalous people to distinguish colors

empijei/empijei-vim 2

A collection of my vim files that are not on github

blogtitle/wapty 1

An open source alternative to burp suite written in Go.

empijei/Attacker-Submitter 1

Software to use during Attack-Defense CTFs

empijei/AuthBear 1

A CLI tool to parse, edit and re-encode "Authorization: Bearer" Tokens

issue openedgolang/go

proposal: add first-class Cross Site Request Forgery protection

Preface

While for other web vulnerabilities the go stdlib provide some quite hardened packages (html/template with autoescaping, net/http with a decent posture against smuggling, tls being easily enforced and older versions not used etc.) there is a lack of first-class support for CSRF protection, and CSRF is the second most prominent vulnerability in modern web applications.

The only bit of library that offers something related to it is x/net/xsrftoken, which is not well known, it is not advertised and it is in the "x" package.

The ecosystem has a proliferation of external packages that provide some form of protection against this vulnerability, so it looks like the current Go stance on this is that if users want to build a secure web application they have to rely on external libraries and frameworks (thus accepting a whole new kind of issues with supply chain attacks) or they have to understand how to build their own protections.

Proposal

I would like to propose to add a first-class supported XSRF protection mechanism in the standard library to address this issue.

Stretch option:

Since it feels to me like CSRF is not the only issue we might have to take care of, I would deem it appropriate to add a security sub-package to net/http (naming and location is open to discussion) to use to collect middleware and other utilities (e.g. creating servers with safe defaults). Go as it is right now almost delivers the full toolkit needed to build secure web applications, but it definitely has what is needed to build a web application, which constitutes a quite scary scenario.

created time in 7 hours

issue openedgolang/go

x/net/xsrftoken: API is error prone

Preface

The only thing that Go provides to protect against CSRF is the x/net/xsrftoken package. This is per-se an issue (will link to this in a follow-up proposal), but there is an additional problem.

The API of said package is the following:

const Timeout
func Generate(key, userID, actionID string) string
func Valid(token, key, userID, actionID string) bool

This is the whole API surface, it is very low-level and requires its users to have a somewhat advanced knowledge of web security to use it properly. I would invite the reader to stop here for a handful of seconds and think how they would use this package to protect a web application, including how they would retrieve the required userID.

The issue

By looking at the naming here a programmer might be inclined to use the XSRF protection only for authenticated users, especially since userID is the name of one of the parameters for both Generate and Valid.

This means that users of this package will probably be vulnerable to Login CSRF. I say this because some colleagues of mine and I analyzed quite a lot of Go web services code we could access and found it consistently vulnerable to some form of CSRF (the maintainers have already been warned and have fixed the issues).

I propose to apply one or more of the following:

  1. Provide a higher-level protection alongside the low-level one: something that works on http handlers and manages cookies/token injection transparently. This would remove the burden of understanding the internals of CSRF from the users and would require less code to be written. As an example there are NoSurf and csrf that have a quite small but significantly harder to misuse API
  2. Add documentation to this package with examples and detailed explanation on how to use it, especially wrt pseudonymous tokens to pass as userID, which from our analysis was one of the most frequent mistakes.
  3. Provide other functions: add some functions to this package. For example helpers to issue and validate csrf-related cookies and inject tokens in html templates. These would basically be the easy-to-use and slightly more versatile building blocks for the first point of this list.

I am willing to do the work for any of these, but I would like to discuss all alternatives and gather some consensus and more ideas before I do.

created time in 7 hours

push eventgoogle/go-safeweb

empijei

commit sha 084ae71f74f683049a84df34cb191c3d20236ba9

safesql: fix doc

view details

empijei

commit sha 85b00b25422102ee53d71407a11b6bafc3886dd5

safesql: create special file for more recent versions of Go, fix typo

view details

empijei

commit sha 08169af35de0fb00c8580a3bfe3fb796dad6c0f8

safesql: gofmt

view details

empijei

commit sha f6548a40fb7960c2f3eae81bfec1aa97de1fb996

safesql: add copyright note to new file.

view details

empijei

commit sha 60d7de8d5aa8000e1325e1fac2ed48804139854b

safesql: remove backticks from doc.

view details

push time in a day

PR merged google/go-safeweb

Reviewers
safesql: fix doc

safesql docs didn't display correctly and were incomplete.

+191 -21

0 comment

3 changed files

empijei

pr closed time in a day

push eventgoogle/go-safeweb

empijei

commit sha 28b4820715fbf0f6fd4ee002282b5b48295a03f5

safesql: remove backticks from doc.

view details

push time in a day

push eventgoogle/go-safeweb

empijei

commit sha edb8bbd625b356954296381a14a94aec6070b437

safesql: add copyright note to new file.

view details

push time in 2 days

push eventgoogle/go-safeweb

empijei

commit sha 344b1bb789516c5ce924d938c1584a7e5a4cf595

safesql: gofmt

view details

push time in 2 days

Pull request review commentgoogle/go-safeweb

safesql: fix doc

 import ( 	"time" ) +// Drivers is a tyny wrapper for https://pkg.go.dev/sql#Drivers func Drivers() []string { return sql.Drivers() } +// Register is a tyny wrapper for https://pkg.go.dev/sql#Register func Register(name string, driver driver.Driver) { sql.Register(name, driver) } +// ColumnType is a tyny wrapper for https://pkg.go.dev/sql#ColumnType type ColumnType = sql.ColumnType++// DBStats is a tyny wrapper for https://pkg.go.dev/sql#DBStats type DBStats = sql.DBStats++// IsolationLevel is a tyny wrapper for https://pkg.go.dev/sql#IsolationLevel type IsolationLevel = sql.IsolationLevel++// NamedArg is a tyny wrapper for https://pkg.go.dev/sql#NamedArg type NamedArg = sql.NamedArg++// NullBool is a tyny wrapper for https://pkg.go.dev/sql#NullBool type NullBool = sql.NullBool++// NullFloat64 is a tyny wrapper for https://pkg.go.dev/sql#NullFloat64 type NullFloat64 = sql.NullFloat64++// NullInt32 is a tyny wrapper for https://pkg.go.dev/sql#NullInt32 type NullInt32 = sql.NullInt32++// NullInt64 is a tyny wrapper for https://pkg.go.dev/sql#NullInt64 type NullInt64 = sql.NullInt64++// NullString is a tyny wrapper for https://pkg.go.dev/sql#NullString type NullString = sql.NullString++// NullTime is a tyny wrapper for https://pkg.go.dev/sql#NullTime type NullTime = sql.NullTime++// Out is a tyny wrapper for https://pkg.go.dev/sql#Out type Out = sql.Out++// RawBytes is a tyny wrapper for https://pkg.go.dev/sql#RawBytes type RawBytes = sql.RawBytes++// Result is a tyny wrapper for https://pkg.go.dev/sql#Result type Result = sql.Result++// Row is a tyny wrapper for https://pkg.go.dev/sql#Row type Row = sql.Row++// Rows is a tyny wrapper for https://pkg.go.dev/sql#Rows type Rows = sql.Rows++// Scanner is a tyny wrapper for https://pkg.go.dev/sql#Scanner type Scanner = sql.Scanner++// Stmt is a tyny wrapper for https://pkg.go.dev/sql#Stmt type Stmt = sql.Stmt++// TxOptions is a tyny wrapper for https://pkg.go.dev/sql#TxOptions type TxOptions = sql.TxOptions  // Conn behaves as the standard SQL package one, with the exception that it does not implement the `Raw` method for security reasons.+// Please see https://pkg.go.dev/sql#Conn type Conn struct { 	c *sql.Conn } -/*-func (c *Conn) Raw(f func(driverConn interface{}) error) (err error) {-	// Dangerous to expose-}-*/+// Begin is a tyny wrapper for https://pkg.go.dev/sql#Conn.Begin func (c Conn) BeginTx(ctx context.Context, opts *TxOptions) (Tx, error) { 	t, err := c.c.BeginTx(ctx, opts) 	return Tx{t}, err }-func (c Conn) Close() error { return c.c.Close() }++// Close is a tyny wrapper for https://pkg.go.dev/sql#Conn.Close+func (c Conn) Close() error {+	return c.c.Close()+}++// ExecContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.ExecContext func (c Conn) ExecContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (Result, error) { 	return c.c.ExecContext(ctx, query.s, args) }-func (c Conn) PingContext(ctx context.Context) error { return c.c.PingContext(ctx) }++// PingContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.PingContext+func (c Conn) PingContext(ctx context.Context) error {+	return c.c.PingContext(ctx)+}++// PrepareContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.PrepareContext func (c Conn) PrepareContext(ctx context.Context, query TrustedSQLString) (*Stmt, error) { 	return c.c.PrepareContext(ctx, query.s) }++// QueryContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.QueryContext func (c Conn) QueryContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (*Rows, error) { 	return c.c.QueryContext(ctx, query.s, args) }++// QueryRowContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.QueryRowContext func (c Conn) QueryRowContext(ctx context.Context, query TrustedSQLString, args ...interface{}) *Row { 	return c.c.QueryRowContext(ctx, query.s, args) }  // DB behaves as the standard SQL package one, with the exception that it does not implement the `Driver` method for security reasons.+// Please see https://pkg.go.dev/sql#DB type DB struct { 	db *sql.DB } +// Open is a tyny wrapper for https://pkg.go.dev/sql#Open func Open(driverName, dataSourceName string) (DB, error) { 	db, err := sql.Open(driverName, dataSourceName) 	return DB{db}, err }++// OpenDB is a tyny wrapper for https://pkg.go.dev/sql#OpenDB func OpenDB(c driver.Connector) DB { return DB{sql.OpenDB(c)} }++// Begin is a tyny wrapper for https://pkg.go.dev/sql#DB.Begin func (db DB) Begin() (Tx, error) { 	t, err := db.db.Begin() 	return Tx{t}, err }++// BeginTx is a tyny wrapper for https://pkg.go.dev/sql#DB.BeginTx func (db DB) BeginTx(ctx context.Context, opts *TxOptions) (Tx, error) { 	t, err := db.db.BeginTx(ctx, opts) 	return Tx{t}, err }-func (db DB) Close() error { return db.db.Close() }++// Close is a tyny wrapper for https://pkg.go.dev/sql#DB.Close+func (db DB) Close() error {+	return db.db.Close()+}++// Conn is a tyny wrapper for https://pkg.go.dev/sql#DB.Conn func (db DB) Conn(ctx context.Context) (Conn, error) { 	c, err := db.db.Conn(ctx) 	return Conn{c}, err }++// Exec is a tyny wrapper for https://pkg.go.dev/sql#DB.Exec func (db DB) Exec(query TrustedSQLString, args ...interface{}) (Result, error) { 	return db.db.Exec(query.s, args) }++// ExecContext is a tyny wrapper for https://pkg.go.dev/sql#DB.ExecContext func (db DB) ExecContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (Result, error) { 	return db.db.ExecContext(ctx, query.s, args) }-func (db DB) Ping() error                                   { return db.db.Ping() }-func (db DB) PingContext(ctx context.Context) error         { return db.db.PingContext(ctx) }-func (db DB) Prepare(query TrustedSQLString) (*Stmt, error) { return db.db.Prepare(query.s) }++// Ping is a tyny wrapper for https://pkg.go.dev/sql#DB.Ping+func (db DB) Ping() error {+	return db.db.Ping()+}++// PingContext is a tyny wrapper for https://pkg.go.dev/sql#DB.PingContext+func (db DB) PingContext(ctx context.Context) error {+	return db.db.PingContext(ctx)+}++// Prepare is a tyny wrapper for https://pkg.go.dev/sql#DB.Prepare+func (db DB) Prepare(query TrustedSQLString) (*Stmt, error) {+	return db.db.Prepare(query.s)+}++// PrepareContext is a tyny wrapper for https://pkg.go.dev/sql#DB.PrepareContext func (db DB) PrepareContext(ctx context.Context, query TrustedSQLString) (*Stmt, error) { 	return db.db.PrepareContext(ctx, query.s) }++// Query is a tyny wrapper for https://pkg.go.dev/sql#DB.Query func (db DB) Query(query TrustedSQLString, args ...interface{}) (*Rows, error) { 	return db.db.Query(query.s, args) }++// QueryContext is a tyny wrapper for https://pkg.go.dev/sql#DB.QueryContext func (db DB) QueryContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (*Rows, error) { 	return db.db.QueryContext(ctx, query.s, args) }++// QueryRow is a tyny wrapper for https://pkg.go.dev/sql#DB.QueryRow func (db DB) QueryRow(query TrustedSQLString, args ...interface{}) *Row { 	return db.db.QueryRow(query.s, args) }++// QueryRowContext is a tyny wrapper for https://pkg.go.dev/sql#DB.QueryRowContext func (db DB) QueryRowContext(ctx context.Context, query TrustedSQLString, args ...interface{}) *Row { 	return db.db.QueryRowContext(ctx, query.s, args) } -// Go 1.15 func (db DB) SetConnMaxIdleTime(d time.Duration) { db.db.SetConnMaxIdleTime(d) }-func (db DB) SetConnMaxLifetime(d time.Duration) { db.db.SetConnMaxLifetime(d) }-func (db DB) SetMaxIdleConns(n int)              { db.db.SetMaxIdleConns(n) }-func (db DB) SetMaxOpenConns(n int)              { db.db.SetMaxOpenConns(n) }-func (db DB) Stats() DBStats                     { return db.db.Stats() }+// Only available in Go 1.15++// func (db DB) SetConnMaxIdleTime(d time.Duration) { db.db.SetConnMaxIdleTime(d) }

Because otherwise this would only compile with super-recent versions of Go.

I moved it to a file that only gets compiled if go 1.15+ is used.

empijei

comment created time in 2 days

PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

safesql: fix doc

 import ( 	"time" ) +// Drivers is a tyny wrapper for https://pkg.go.dev/sql#Drivers

Done.

empijei

comment created time in 2 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

empijei

commit sha c9ca3e4b4bedb46ec89bbac770a868ce077e444b

safesql: create special file for more recent versions of Go, fix typo

view details

push time in 2 days

issue commentgolang/go

net/http: SameSiteDefaultMode adds an incorrect 'Set-Cookie' attribute

forced the new behavior too soon and Apple did not backport

The fact that on Apple browsers SameSite=None behaved as SameSite=Lax was a bug, it's not really about backporting but more like Apple not really caring about what FF and Chrome do or what the spec says (aka: unknown values should be ignored). Nowadays Safari enforces something similar to SameSite=Lax by default and they pushed it to all users in a completely careless way, not putting much thought about breakages and bragging about having solved CSRF (which they didn't). They can because the user base is small and people can always count on compliant, backup browsers in case stuff doesn't work.

so enforcement had to be postponed anyway

Yes, it was because the breakage would have been too big since Apple was lagging too much behind (two full years) the current spec and, unlike the expectation towards Safari, people rely on Chrome to always work.

I'm not sure what makes this inconsiderate behavior so seductive in Google

My personal guess: trying to protect users from CSRF in a widespread way, since the issue has been there for very long it would just be inconsiderate to leave that dangerous issue there when there is a way for user agents to fix it (exactly like Safari did, please see the the links above). Please note this is a personal guess and not an official Google stance.

I strongly disagree How much do we hurry/can we get this in go1.16?

I am fine with rolling back my change and keep discussing this here, I am not in a rush to get this fixed and I would like to find something that works for everyone.

If I get this right your proposal is:

  • The SameSiteDefault value will keep its current behavior when cookies are set, but the value will have a deprecation note that explains why this should not be used for modern browsers.
  • We add SameSiteUnknown and SameSiteUnset values that will only be used during parsing. SameSiteDefault will not be used during parsing

My question would be: if someone wrote a server 3 years ago and used SameSiteDefault their intention would have been to use what today is called SameSiteLax. How do we make it so that code, when run with modern versions of Go and modern browsers maintains the intended behavior?

euank

comment created time in 2 days

PR opened google/go-safeweb

safesql: fix doc

safesql docs didn't display correctly and were incomplete.

+168 -21

0 comment

2 changed files

pr created time in 3 days

create barnchgoogle/go-safeweb

branch : safesql-docs

created branch time in 3 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha c943ca9caa47267667393018b891198a56663af3

Implemented SetStatus that allows setting a custom response status

view details

Mara Mihali

commit sha 3719462e3e65a0e45d98056e4a89243b75fcb0cf

Document the case in which NoContent, Redirect or WriteError are called after SetCode

view details

Mara Mihali

commit sha 5087bd49b0bf5f5f16b88e927122d617d743ea4a

Modify SetCode to not panic if it's called after the response was already written

view details

push time in 3 days

PR merged google/go-safeweb

Allow the user to set a custom status code in the safehttp.ResponseWriter core enhancement hacktoberfest-accepted

Fixes #151

Previously, there was no way of setting a status code other than safehttp.NoContent as the write method in the safehttp.ResponseWriter would always set the status code to safehttp.StatusOK. Implemented initial functionality and tests to make this possible.

+126 -1

3 comments

2 changed files

maramihali

pr closed time in 3 days

issue closedgoogle/go-safeweb

Add support to set 2XX statuses that are not 200 and 204

There currently is no way to set any of the 2XX statuses, so Partial Content is not doable.

closed time in 3 days

empijei

issue closedgoogle/go-safeweb

Should ResponseWriter.SetCode allow setting a 3XX or 4XX-5XX status code?

Ideally, this should only be allowed by calling the Redirect and WriteError methods so we need to decide what should happen if a 3XX-5XX status code is passed to SetCode (preferably not panic).

closed time in 3 days

maramihali
PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

Allow the user to set a custom status code in the safehttp.ResponseWriter

 func (w *ResponseWriter) SetCookie(c *Cookie) error { 	return w.header.addCookie(c) } +// SetCode allows setting a response status. This method will panic if an+// invalid status code is passed (i.e. not in the range 1XX-5XX) or if the+// response has already been written.+//+// If SetCode was called before NoContent, Redirect or WriteError, the status+// code set by the latter will be the actual response status.+//+// TODO(empijei@, kele@, maramihali@): decide what should be done if the+// code passed is either 3XX (redirect) or 4XX-5XX (client/server error).+func (w *ResponseWriter) SetCode(code StatusCode) {+	if w.written {+		panic("ResponseWriter was already written to")+	}

I would not panic here. I would actually do nothing in this case. This is not an error worth panicking for, and if we have already indeed written something it's not like we can change the status to a 500, so I would just do nothing in this case and document that if we have already written calling SetCode does nothing.

This would likely either be a non-issue or a small bug, definitely hard to find this as an exploitable vulnerability, so I would not invest too much in making this hard to get wrong.

maramihali

comment created time in 3 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha 523a9ec7e2c86024b660a991cec76300c6cba525

Implemented support for Angular's XHR XSRF protection

view details

Mara Mihali

commit sha 2c13ec723e2e5a8c5be73d609cdce2b4c8a760fc

Ensure the XSRF plugins satisfy the safehttp.Interceptor interface by adding the OnError methods

view details

Mara Mihali

commit sha ff89e5e719f37188f76352bf81aa27ca651db20b

Change the directory tree to have both XSRF plugins inside the same one and created file that contains helper functions used by both. Refactored some logic according to code review

view details

Mara Mihali

commit sha 0503c0abe991366824860aec4207418b478729cd

Refactor documentation and added a Default function that sets the TokenCookieName and TokenHeaderName to their default values as indicated by the documentation

view details

Mara Mihali

commit sha 4e4f83f0cf777201902e5946439ae3407f0f8f33

Use the time package to specify the timeout for the Angular token cookie

view details

Mara Mihali

commit sha 5f30307c2d032d9afacf9283480a37e737576121

Refactor documentation and variable names according to code review

view details

Mara Mihali

commit sha ec2d904e9030409205d40d286887b3d8154e8d0f

Improve test coverage

view details

Mara Mihali

commit sha 77271ed38fcc1877284e23ce770d0dc0df133b70

Simplify test data creation

view details

Mara Mihali

commit sha cdc5cf3085bd5389678ea83a720b9b3bd10d5071

Change the Func names in the CSP and html XSRF plugin with the ones exported by htmlinject

view details

Mara Mihali

commit sha 0f92e44457f5cf240b87537d23d6bd423e58986b

Small documentation refactoring according to code review

view details

push time in 5 days

PR merged google/go-safeweb

Reviewers
XSRF plugin for Angular enhancement hacktoberfest-accepted plugin

Fixes #194

Created a separate plugin that provides protection against XSRF in Angular. More details can be found here: https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection

Changed the directory tree to include the two XSRF plugins in the same directory with the functionality used by both placed in a separate file.

+515 -147

1 comment

9 changed files

maramihali

pr closed time in 5 days

issue closedgoogle/go-safeweb

Extend the XSRF plugin to provide protection for Angular XHR

See: https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details on how this can be acheived.

closed time in 5 days

maramihali

Pull request review commentgoogle/go-safeweb

XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfhtml++import (+	"context"+	"crypto/rand"+	"encoding/base64"+	"errors"+	"fmt"+	"github.com/google/go-safeweb/safehttp/plugins/xsrf"++	"github.com/google/go-safeweb/safehttp"+	"golang.org/x/net/xsrftoken"+)++const (+	// TokenKey is the form key used when sending the token as part of POST+	// request.+	TokenKey    = "xsrf-token"+	cookieIDKey = "xsrf-cookie"+)++// Interceptor implements XSRF protection.+type Interceptor struct {+	// SecretAppKey uniquely identifies each registered service and should have+	// high entropy as it is used for generating the XSRF token.+	SecretAppKey string+}++var _ safehttp.Interceptor = &Interceptor{}++type tokenCtxKey struct{}++// Token extracts the XSRF token from the incoming request. If it is not+// present, it returns a non-nil error.+func Token(r *safehttp.IncomingRequest) (string, error) {+	tok := r.Context().Value(tokenCtxKey{})+	if tok == nil {+		return "", errors.New("xsrf token not found")+	}+	return tok.(string), nil+}++func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {+	buf := make([]byte, 20)+	if _, err := rand.Read(buf); err != nil {+		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+	}++	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))+	c.SetSameSite(safehttp.SameSiteStrictMode)+	if err := w.SetCookie(c); err != nil {+		return nil, err+	}+	return c, nil+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request+// Forgery attack.+//+// On first user visit through a state preserving request (GET, HEAD or+// OPTIONS), a nonce-based cookie will be set in the response as a way to+// distinguish between users and prevent pre-login XSRF attacks. The cookie will+// be used in the token generation and verification algorithm and is expected to+// be present in all subsequent incoming requests.+//+// For every authorized request, the interceptor will also generate a+// cryptographically-safe XSRF token using the appKey, the cookie and the path+// visited. This can be later extracted using Token and should be injected as a+// hidden input field in HTML forms.+//+// In case of state changing requests (all except GET, HEAD and OPTIONS), the+// interceptor checks for the presence of the XSRF token in the request body+// (expected to have been injected) and validates it.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	needsValidation := !xsrf.StatePreserving(r)

Sounds good to me.

maramihali

comment created time in 5 days

PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfhtml++import (+	"context"+	"crypto/rand"+	"encoding/base64"+	"errors"+	"fmt"+	"github.com/google/go-safeweb/safehttp/plugins/xsrf"++	"github.com/google/go-safeweb/safehttp"+	"golang.org/x/net/xsrftoken"+)++const (+	// TokenKey is the form key used when sending the token as part of POST+	// request.+	TokenKey    = "xsrf-token"+	cookieIDKey = "xsrf-cookie"+)++// Interceptor implements XSRF protection.+type Interceptor struct {+	// SecretAppKey uniquely identifies each registered service and should have+	// high entropy as it is used for generating the XSRF token.+	SecretAppKey string+}++var _ safehttp.Interceptor = &Interceptor{}++type tokenCtxKey struct{}++// Token extracts the XSRF token from the incoming request. If it is not+// present, it returns a non-nil error.+func Token(r *safehttp.IncomingRequest) (string, error) {+	tok := r.Context().Value(tokenCtxKey{})+	if tok == nil {+		return "", errors.New("xsrf token not found")+	}+	return tok.(string), nil+}++func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {+	buf := make([]byte, 20)+	if _, err := rand.Read(buf); err != nil {+		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+	}++	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))+	c.SetSameSite(safehttp.SameSiteStrictMode)+	if err := w.SetCookie(c); err != nil {+		return nil, err+	}+	return c, nil+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request+// Forgery attack.+//+// On first user visit through a state preserving request (GET, HEAD or+// OPTIONS), a nonce-based cookie will be set in the response as a way to+// distinguish between users and prevent pre-login XSRF attacks. The cookie will+// be used in the token generation and verification algorithm and is expected to+// be present in all subsequent incoming requests.+//+// For every authorized request, the interceptor will also generate a+// cryptographically-safe XSRF token using the appKey, the cookie and the path+// visited. This can be later extracted using Token and should be injected as a+// hidden input field in HTML forms.+//+// In case of state changing requests (all except GET, HEAD and OPTIONS), the+// interceptor checks for the presence of the XSRF token in the request body+// (expected to have been injected) and validates it.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	needsValidation := !xsrf.StatePreserving(r)+	cookieID, err := r.Cookie(cookieIDKey)+	if err != nil {+		if needsValidation {+			return w.WriteError(safehttp.StatusForbidden)+		}+		cookieID, err = addCookieID(w)+		if err != nil {+			// An error is returned when the plugin fails to Set the Set-Cookie+			// header in the response writer as this is a server misconfiguration.+			return w.WriteError(safehttp.StatusInternalServerError)+		}+	}++	actionID := r.URL.Path()+	if needsValidation {+		f, err := r.PostForm()+		if err != nil {+			// We fallback to checking whether the form is multipart. Both types+			// are valid in an incoming request as long as the XSRF token is+			// present.+			mf, err := r.MultipartForm(32 << 20)+			if err != nil {+				return w.WriteError(safehttp.StatusBadRequest)+			}+			f = &mf.Form+		}++		tok := f.String(TokenKey, "")+		if f.Err() != nil || tok == "" {+			return w.WriteError(safehttp.StatusUnauthorized)+		}++		if ok := xsrftoken.Valid(tok, it.SecretAppKey, cookieID.Value(), actionID); !ok {+			return w.WriteError(safehttp.StatusForbidden)+		}+	}++	tok := xsrftoken.Generate(it.SecretAppKey, cookieID.Value(), actionID)+	r.SetContext(context.WithValue(r.Context(), tokenCtxKey{}, tok))+	return safehttp.NotWritten()+}++// Commit adds the XSRF token corresponding to the safehttp.TemplateResponse+// with key "XSRFToken". The token corresponds to the user information found in+// the request.+func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	tmplResp, ok := resp.(safehttp.TemplateResponse)+	if !ok {+		return safehttp.NotWritten()+	}++	tok, err := Token(r)+	if err != nil {+		// The token should have been added in the Before stage and if that is+		// not the case, a server misconfiguration occured.+		return w.WriteError(safehttp.StatusInternalServerError)+	}++	// TODO(maramihali@): Change the key when function names are exported by+	// htmlinject

They are exported now.

maramihali

comment created time in 5 days

Pull request review commentgoogle/go-safeweb

XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfhtml++import (+	"context"+	"crypto/rand"+	"encoding/base64"+	"errors"+	"fmt"+	"github.com/google/go-safeweb/safehttp/plugins/xsrf"++	"github.com/google/go-safeweb/safehttp"+	"golang.org/x/net/xsrftoken"+)++const (+	// TokenKey is the form key used when sending the token as part of POST+	// request.+	TokenKey    = "xsrf-token"+	cookieIDKey = "xsrf-cookie"+)++// Interceptor implements XSRF protection.+type Interceptor struct {+	// SecretAppKey uniquely identifies each registered service and should have+	// high entropy as it is used for generating the XSRF token.+	SecretAppKey string+}++var _ safehttp.Interceptor = &Interceptor{}++type tokenCtxKey struct{}++// Token extracts the XSRF token from the incoming request. If it is not+// present, it returns a non-nil error.+func Token(r *safehttp.IncomingRequest) (string, error) {+	tok := r.Context().Value(tokenCtxKey{})+	if tok == nil {+		return "", errors.New("xsrf token not found")+	}+	return tok.(string), nil+}++func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {+	buf := make([]byte, 20)+	if _, err := rand.Read(buf); err != nil {+		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+	}++	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))+	c.SetSameSite(safehttp.SameSiteStrictMode)+	if err := w.SetCookie(c); err != nil {+		return nil, err+	}+	return c, nil+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request+// Forgery attack.+//+// On first user visit through a state preserving request (GET, HEAD or+// OPTIONS), a nonce-based cookie will be set in the response as a way to+// distinguish between users and prevent pre-login XSRF attacks. The cookie will+// be used in the token generation and verification algorithm and is expected to+// be present in all subsequent incoming requests.+//+// For every authorized request, the interceptor will also generate a+// cryptographically-safe XSRF token using the appKey, the cookie and the path+// visited. This can be later extracted using Token and should be injected as a+// hidden input field in HTML forms.+//+// In case of state changing requests (all except GET, HEAD and OPTIONS), the+// interceptor checks for the presence of the XSRF token in the request body+// (expected to have been injected) and validates it.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	needsValidation := !xsrf.StatePreserving(r)

Please make this behave as the other code: as a first thing we do what we'll need to do every time: generate the token and add it to the context.

Then if the request is state preserving we just return notwritten, otherwise we continue with the checks.

The code below has a lot of nested ifs and this makes it hard to follow.

maramihali

comment created time in 5 days

Pull request review commentgoogle/go-safeweb

XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfangular++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+	"github.com/google/go-safeweb/safehttp/plugins/xsrf"+	"time"+)++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the session cookie that holds the XSRF+	// token.+	TokenCookieName string+	// TokenHeaderName is the name of the HTTP header that holds the XSRF token.+	TokenHeaderName string+}++var _ safehttp.Interceptor = &Interceptor{}++// Default creates an Interceptor with TokenCookieName set to XSRF-TOKEN and+// TokenHeaderName set to X-XSRF-TOKEN, their default values. However, in order+// to prevent collisions when multiple applications share the same domain or+// subdomain, each application should set a unique name for the cookie.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+func Default() *Interceptor {+	return &Interceptor{+		TokenCookieName: "XSRF-TOKEN",+		TokenHeaderName: "X-XSRF-TOKEN",+	}+}++// Before checks for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	if xsrf.StatePreserving(r) {+		return safehttp.NotWritten()+	}++	c, err := r.Cookie(it.TokenCookieName)+	if err != nil || c.Value() == "" {+		return w.WriteError(safehttp.StatusForbidden)+	}++	tok := r.Header.Get(it.TokenHeaderName)+	if tok == "" || tok != c.Value() {+		// JavaScript has access only to cookies from the domain it's running+		// on. Hence, if the same token is found in both the cookie and the+		// header, the request can be trusted.+		return w.WriteError(safehttp.StatusUnauthorized)+	}++	return safehttp.NotWritten()+}++func (it *Interceptor) addTokenCookie(w *safehttp.ResponseWriter) error {+	tok := make([]byte, 20)+	if _, err := rand.Read(tok); err != nil {+		return fmt.Errorf("crypto/rand.Read: %v", err)+	}+	c := safehttp.NewCookie(it.TokenCookieName, base64.StdEncoding.EncodeToString(tok))++	c.SetSameSite(safehttp.SameSiteStrictMode)+	c.SetPath("/")+	day := 24 * time.Hour+	c.SetMaxAge(int(day.Seconds()))+	// Needed in order to make the cookie accessible by JavaScript+	// running on the same domain.+	c.DisableHTTPOnly()++	return w.SetCookie(c)+}++// Commit generates a cryptographically secure random cookie on the first state+// preserving request (GET, HEAD or OPTION) and sets it in the response. On+// every subsequent request the cookie is expected alongside a header that+// matches its value.+func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	if c, err := r.Cookie(it.TokenCookieName); err == nil && c.Value() != "" {+		// The XSRF cookie is there so we don't need to do anything else.+		return safehttp.NotWritten()+	}++	if !xsrf.StatePreserving(r) {+		// This should never happen as, if this is a state-changing request and+		// it lacks the cookie, it would've been already rejected by Before.+		return w.WriteError(safehttp.StatusInternalServerError)+	}++	err := it.addTokenCookie(w)+	if err != nil {
	if err := it.addTokenCookie(w); err != nil {
maramihali

comment created time in 5 days

Pull request review commentgoogle/go-safeweb

XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfangular++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+	"github.com/google/go-safeweb/safehttp/plugins/xsrf"+	"time"+)++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the session cookie that holds the XSRF+	// token.
	// TokenCookieName is the name of the session cookie that holds the XSRF token.
maramihali

comment created time in 5 days

PullRequestReviewEvent
PullRequestReviewEvent

issue commentgolang/go

net/http: SameSiteDefaultMode adds an incorrect 'Set-Cookie' attribute

Modern browsers ignore any SameSite attribute that doesn't have a value or that has an unknown value so we should probably do the same and set it to the new proposed Unset value.

+1 on Deprecating +1 on updating the spec +1 on having a name for the Unset value -1 on supporting the old behavior, I think if you are doing User-Agent sniffing on the server side and detect a very old browser you can just disable SameSite completely for that specific request. This is what most servers already do when they see unsupported user agents and serves as a good driver to make vendors implement or backport new specifications. I personally don't thing it does any good to keep supporting platforms that have stopped receiving updates from their own producers.

If this is good enough for you, you might want to try and take a stab at this.

euank

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results? -There are several types of XS-Leaks, and they have distinct origins:+In a vulnerable application, the above might be equivalent to: -- [`HTML` APIs]({{< ref "frame-counting.md" >}}) that allow attackers to access information about a page-- [Browser Features]({{< ref "../docs/attacks/historical/stateful-browser-features.md" >}}) which unintendedly introduced XS-Leaks-- [Browser Bugs]({{< ref "../docs/attacks/historical/Content-Type.md#typemustmatch" >}})-- [Inherent Web Platform Features]({{< ref "frame-counting.md" >}}) (or a combination of them)-- [Hardware Limitations]({{< ref "../docs/attacks/timing-attacks/connection-pool.md" >}})+> Does a query *?query=existent* return HTTP200 status code while *?query=non-existent* does not?" -# About the Wiki+The latter oracle could be formed from an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) XS-Leak and which could be abused by attackers to infer information about the user. -This Wiki contains an introduction to XS-Leaks enriched with in-depth information. A reader is expected to be able to understand the topic, discover internals of some attacks and how to defend an application against them.-This Wiki aggregates most of the XS-Leaks described and discovered in the wild but frequently new ones emerge. Please feel free to help this Wiki grow and be up to date by contributing to the [XS-Leaks Github Repository](https://github.com/xsleaks/wiki).++Browsers provide tons of different APIs that while well-intentioned, can end up leaking small amounts of information in an unintended way.++## Example++Websites are generally not allowed to access data on other websites. For example, *evil.com* is forbidden from explicitly reading a response from *bank.com*, but *evil.com* can attempt to load a script from *bank.com* since that was originally seen as harmless [^harmless]. However, *evil.com* can also determine whether or not the script successfully loaded.++{{< hint info >}}++Suppose that *bank.com* has an API endpoint that returns data about a user's receipt for a given query.++1. The page *evil.com* could attempt to load the URL *bank.com/my_receipt?q=groceries* as a script.+2. If the user has recently bought groceries, it will load successfully (because of the HTTP200 status code).+3. But if they haven't, it will trigger an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) (because of the HTTP404 status code) that *evil.com* can watch for.+4. By repeating this with different queries, the attacker could infer a significant amount of information about the user's transaction history.+{{< /hint >}}++In the above example, two websites on two different origins (*evil.com* and *bank.com*) were interacting through an API that browsers allow websites to use. None of this exploited a bug in the browser or a bug in *bank.com*. But nonetheless, it allowed *evil.com* to gain some small amount of information about the user on *bank.com*.  ++++## Root Cause of XS-Leaks++The root cause of most XS-Leaks is inherent to the design of the web. Oftentimes, applications are vulnerable to some cross-site information leaks without having explicitly done anything wrong. Because it is hard [^hard-to-fix] to universally fix the root cause of XS-Leaks at the browser level, browsers are implementing various [Defense Mechanisms]({{< ref "defenses" >}}) that offer applications ways of mitigating some of the techniques. Many of these mitigations are used via websites opting into a more restrictive security model, usually through certain HTTP headers (e.g. *[Cross-Origin-Opener]({{< ref "./docs/defenses/opt-in/coop.md">}}): same-origin*), which often must be combined to achieve the desired outcome.++We can distinguish different sources of XS-Leaks, such as:++-   Browser APIs +    -   For example: [Frame Counting]({{< ref "frame-counting.md" >}}) and [Timing Attacks]({{< ref "timing-attacks.md" >}})++-   Browser Implementation Details and Bugs+    -   For example: [Connection Pooling]({{< ref "./docs/attacks/timing-attacks/connection-pool.md" >}}) and [typeMustMatch]({{< ref "./docs/attacks/historical/content-type.md#typemustmatch" >}})++-   Hardware Bugs+    -   For example: Speculative Execution Attacks [^spectre]++## A little bit of history++XS-Leaks have always been part of the web platform but have only gained attention in recent years [^old-wiki] as websites became more secure against more traditional classes of vulnerabilities. In 2015 Gelernter and Herzberg published Cross-Site Search Attacks [^xs-search-first] which covered their work on exploiting timing attacks to implement XS-Search attacks against Google and Microsoft. From there, more XS-Leak techniques were discovered and tested over time. Recently, browsers have been working on developing a variety of new standards that will make it easier to defend applications against XS-Leaks.++## About the wiki+++This wiki is meant to both introduce readers to XS-Leaks and serve as a reference guide to experienced researchers exploiting XS-Leaks. While this wiki contains information on many different techniques, new techniques are always emerging. Improvements, whether to add new techniques or improve existing pages, are always appreciated!
This wiki is meant to both introduce readers to XS-Leaks and serve as a reference guide to experienced researchers exploiting XS-Leaks. While this wiki contains information on many different techniques, new techniques are always emerging. Improvements, whether to add new techniques or expand existing pages, are always appreciated!

I generally try to explain or expand on a concept by re-using the same word I used for that concept.

terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results? -There are several types of XS-Leaks, and they have distinct origins:+In a vulnerable application, the above might be equivalent to: -- [`HTML` APIs]({{< ref "frame-counting.md" >}}) that allow attackers to access information about a page-- [Browser Features]({{< ref "../docs/attacks/historical/stateful-browser-features.md" >}}) which unintendedly introduced XS-Leaks-- [Browser Bugs]({{< ref "../docs/attacks/historical/Content-Type.md#typemustmatch" >}})-- [Inherent Web Platform Features]({{< ref "frame-counting.md" >}}) (or a combination of them)-- [Hardware Limitations]({{< ref "../docs/attacks/timing-attacks/connection-pool.md" >}})+> Does a query *?query=existent* return HTTP200 status code while *?query=non-existent* does not?" -# About the Wiki+The latter oracle could be formed from an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) XS-Leak and which could be abused by attackers to infer information about the user. -This Wiki contains an introduction to XS-Leaks enriched with in-depth information. A reader is expected to be able to understand the topic, discover internals of some attacks and how to defend an application against them.-This Wiki aggregates most of the XS-Leaks described and discovered in the wild but frequently new ones emerge. Please feel free to help this Wiki grow and be up to date by contributing to the [XS-Leaks Github Repository](https://github.com/xsleaks/wiki).++Browsers provide tons of different APIs that while well-intentioned, can end up leaking small amounts of information in an unintended way.++## Example++Websites are generally not allowed to access data on other websites. For example, *evil.com* is forbidden from explicitly reading a response from *bank.com*, but *evil.com* can attempt to load a script from *bank.com* since that was originally seen as harmless [^harmless]. However, *evil.com* can also determine whether or not the script successfully loaded.++{{< hint info >}}++Suppose that *bank.com* has an API endpoint that returns data about a user's receipt for a given query.++1. The page *evil.com* could attempt to load the URL *bank.com/my_receipt?q=groceries* as a script.+2. If the user has recently bought groceries, it will load successfully (because of the HTTP200 status code).+3. But if they haven't, it will trigger an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) (because of the HTTP404 status code) that *evil.com* can watch for.+4. By repeating this with different queries, the attacker could infer a significant amount of information about the user's transaction history.+{{< /hint >}}++In the above example, two websites on two different origins (*evil.com* and *bank.com*) were interacting through an API that browsers allow websites to use. None of this exploited a bug in the browser or a bug in *bank.com*. But nonetheless, it allowed *evil.com* to gain some small amount of information about the user on *bank.com*.  ++++## Root Cause of XS-Leaks++The root cause of most XS-Leaks is inherent to the design of the web. Oftentimes, applications are vulnerable to some cross-site information leaks without having explicitly done anything wrong. Because it is hard [^hard-to-fix] to universally fix the root cause of XS-Leaks at the browser level, browsers are implementing various [Defense Mechanisms]({{< ref "defenses" >}}) that offer applications ways of mitigating some of the techniques. Many of these mitigations are used via websites opting into a more restrictive security model, usually through certain HTTP headers (e.g. *[Cross-Origin-Opener]({{< ref "./docs/defenses/opt-in/coop.md">}}): same-origin*), which often must be combined to achieve the desired outcome.++We can distinguish different sources of XS-Leaks, such as:++-   Browser APIs +    -   For example: [Frame Counting]({{< ref "frame-counting.md" >}}) and [Timing Attacks]({{< ref "timing-attacks.md" >}})++-   Browser Implementation Details and Bugs+    -   For example: [Connection Pooling]({{< ref "./docs/attacks/timing-attacks/connection-pool.md" >}}) and [typeMustMatch]({{< ref "./docs/attacks/historical/content-type.md#typemustmatch" >}})++-   Hardware Bugs+    -   For example: Speculative Execution Attacks [^spectre]++## A little bit of history++XS-Leaks have always been part of the web platform but have only gained attention in recent years [^old-wiki] as websites became more secure against more traditional classes of vulnerabilities. In 2015 Gelernter and Herzberg published Cross-Site Search Attacks [^xs-search-first] which covered their work on exploiting timing attacks to implement XS-Search attacks against Google and Microsoft. From there, more XS-Leak techniques were discovered and tested over time. Recently, browsers have been working on developing a variety of new standards that will make it easier to defend applications against XS-Leaks.++## About the wiki++
terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results? -There are several types of XS-Leaks, and they have distinct origins:+In a vulnerable application, the above might be equivalent to: -- [`HTML` APIs]({{< ref "frame-counting.md" >}}) that allow attackers to access information about a page-- [Browser Features]({{< ref "../docs/attacks/historical/stateful-browser-features.md" >}}) which unintendedly introduced XS-Leaks-- [Browser Bugs]({{< ref "../docs/attacks/historical/Content-Type.md#typemustmatch" >}})-- [Inherent Web Platform Features]({{< ref "frame-counting.md" >}}) (or a combination of them)-- [Hardware Limitations]({{< ref "../docs/attacks/timing-attacks/connection-pool.md" >}})+> Does a query *?query=existent* return HTTP200 status code while *?query=non-existent* does not?" -# About the Wiki+The latter oracle could be formed from an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) XS-Leak and which could be abused by attackers to infer information about the user. -This Wiki contains an introduction to XS-Leaks enriched with in-depth information. A reader is expected to be able to understand the topic, discover internals of some attacks and how to defend an application against them.-This Wiki aggregates most of the XS-Leaks described and discovered in the wild but frequently new ones emerge. Please feel free to help this Wiki grow and be up to date by contributing to the [XS-Leaks Github Repository](https://github.com/xsleaks/wiki).++Browsers provide tons of different APIs that while well-intentioned, can end up leaking small amounts of information in an unintended way.++## Example++Websites are generally not allowed to access data on other websites. For example, *evil.com* is forbidden from explicitly reading a response from *bank.com*, but *evil.com* can attempt to load a script from *bank.com* since that was originally seen as harmless [^harmless]. However, *evil.com* can also determine whether or not the script successfully loaded.++{{< hint info >}}++Suppose that *bank.com* has an API endpoint that returns data about a user's receipt for a given query.++1. The page *evil.com* could attempt to load the URL *bank.com/my_receipt?q=groceries* as a script.+2. If the user has recently bought groceries, it will load successfully (because of the HTTP200 status code).+3. But if they haven't, it will trigger an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) (because of the HTTP404 status code) that *evil.com* can watch for.+4. By repeating this with different queries, the attacker could infer a significant amount of information about the user's transaction history.+{{< /hint >}}++In the above example, two websites on two different origins (*evil.com* and *bank.com*) were interacting through an API that browsers allow websites to use. None of this exploited a bug in the browser or a bug in *bank.com*. But nonetheless, it allowed *evil.com* to gain some small amount of information about the user on *bank.com*.  ++++## Root Cause of XS-Leaks++The root cause of most XS-Leaks is inherent to the design of the web. Oftentimes, applications are vulnerable to some cross-site information leaks without having explicitly done anything wrong. Because it is hard [^hard-to-fix] to universally fix the root cause of XS-Leaks at the browser level, browsers are implementing various [Defense Mechanisms]({{< ref "defenses" >}}) that offer applications ways of mitigating some of the techniques. Many of these mitigations are used via websites opting into a more restrictive security model, usually through certain HTTP headers (e.g. *[Cross-Origin-Opener]({{< ref "./docs/defenses/opt-in/coop.md">}}): same-origin*), which often must be combined to achieve the desired outcome.
The root cause of most XS-Leaks is inherent to the design of the web. Oftentimes applications are vulnerable to some cross-site information leaks without having done anything wrong. Because it is hard [^hard-to-fix] to universally fix the root cause of XS-Leaks at the browser level, browsers are implementing various [Defense Mechanisms]({{< ref "defenses" >}}) that offer applications ways of mitigating some of the issues. Many of these mitigations are used via websites opting into a more restrictive security model, usually through certain HTTP headers (e.g. *[Cross-Origin-Opener-Policy]({{< ref "./docs/defenses/opt-in/coop.md">}}): same-origin*), which often times must be combined to achieve the desired outcome.
terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results? -There are several types of XS-Leaks, and they have distinct origins:+In a vulnerable application, the above might be equivalent to: -- [`HTML` APIs]({{< ref "frame-counting.md" >}}) that allow attackers to access information about a page-- [Browser Features]({{< ref "../docs/attacks/historical/stateful-browser-features.md" >}}) which unintendedly introduced XS-Leaks-- [Browser Bugs]({{< ref "../docs/attacks/historical/Content-Type.md#typemustmatch" >}})-- [Inherent Web Platform Features]({{< ref "frame-counting.md" >}}) (or a combination of them)-- [Hardware Limitations]({{< ref "../docs/attacks/timing-attacks/connection-pool.md" >}})+> Does a query *?query=existent* return HTTP200 status code while *?query=non-existent* does not?" -# About the Wiki+The latter oracle could be formed from an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) XS-Leak and which could be abused by attackers to infer information about the user. -This Wiki contains an introduction to XS-Leaks enriched with in-depth information. A reader is expected to be able to understand the topic, discover internals of some attacks and how to defend an application against them.-This Wiki aggregates most of the XS-Leaks described and discovered in the wild but frequently new ones emerge. Please feel free to help this Wiki grow and be up to date by contributing to the [XS-Leaks Github Repository](https://github.com/xsleaks/wiki).++Browsers provide tons of different APIs that while well-intentioned, can end up leaking small amounts of information in an unintended way.++## Example++Websites are generally not allowed to access data on other websites. For example, *evil.com* is forbidden from explicitly reading a response from *bank.com*, but *evil.com* can attempt to load a script from *bank.com* since that was originally seen as harmless [^harmless]. However, *evil.com* can also determine whether or not the script successfully loaded.++{{< hint info >}}++Suppose that *bank.com* has an API endpoint that returns data about a user's receipt for a given query.++1. The page *evil.com* could attempt to load the URL *bank.com/my_receipt?q=groceries* as a script.+2. If the user has recently bought groceries, it will load successfully (because of the HTTP200 status code).+3. But if they haven't, it will trigger an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) (because of the HTTP404 status code) that *evil.com* can watch for.+4. By repeating this with different queries, the attacker could infer a significant amount of information about the user's transaction history.+{{< /hint >}}++In the above example, two websites on two different origins (*evil.com* and *bank.com*) were interacting through an API that browsers allow websites to use. None of this exploited a bug in the browser or a bug in *bank.com*. But nonetheless, it allowed *evil.com* to gain some small amount of information about the user on *bank.com*.  ++++## Root Cause of XS-Leaks++The root cause of most XS-Leaks is inherent to the design of the web. Oftentimes, applications are vulnerable to some cross-site information leaks without having explicitly done anything wrong. Because it is hard [^hard-to-fix] to universally fix the root cause of XS-Leaks at the browser level, browsers are implementing various [Defense Mechanisms]({{< ref "defenses" >}}) that offer applications ways of mitigating some of the techniques. Many of these mitigations are used via websites opting into a more restrictive security model, usually through certain HTTP headers (e.g. *[Cross-Origin-Opener]({{< ref "./docs/defenses/opt-in/coop.md">}}): same-origin*), which often must be combined to achieve the desired outcome.++We can distinguish different sources of XS-Leaks, such as:++-   Browser APIs +    -   For example: [Frame Counting]({{< ref "frame-counting.md" >}}) and [Timing Attacks]({{< ref "timing-attacks.md" >}})++-   Browser Implementation Details and Bugs+    -   For example: [Connection Pooling]({{< ref "./docs/attacks/timing-attacks/connection-pool.md" >}}) and [typeMustMatch]({{< ref "./docs/attacks/historical/content-type.md#typemustmatch" >}})++-   Hardware Bugs+    -   For example: Speculative Execution Attacks [^spectre]++## A little bit of history++XS-Leaks have always been part of the web platform but have only gained attention in recent years [^old-wiki] as websites became more secure against more traditional classes of vulnerabilities. In 2015 Gelernter and Herzberg published Cross-Site Search Attacks [^xs-search-first] which covered their work on exploiting timing attacks to implement XS-Search attacks against Google and Microsoft. From there, more XS-Leak techniques were discovered and tested over time. Recently, browsers have been working on developing a variety of new standards that will make it easier to defend applications against XS-Leaks.
XS-Leaks have always been part of the web platform but have only gained attention in recent years [^old-wiki] as websites became more secure against more traditional classes of vulnerabilities. In 2015 Gelernter and Herzberg published "Cross-Site Search Attacks" [^xs-search-first] which covered their work on exploiting timing attacks to implement XS-Search attacks against Google and Microsoft. From there, more XS-Leak techniques were discovered and tested over time. Recently, browsers have been working on developing a variety of new standards that will make it easier to defend applications against XS-Leaks.
terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results? -There are several types of XS-Leaks, and they have distinct origins:+In a vulnerable application, the above might be equivalent to: -- [`HTML` APIs]({{< ref "frame-counting.md" >}}) that allow attackers to access information about a page-- [Browser Features]({{< ref "../docs/attacks/historical/stateful-browser-features.md" >}}) which unintendedly introduced XS-Leaks-- [Browser Bugs]({{< ref "../docs/attacks/historical/Content-Type.md#typemustmatch" >}})-- [Inherent Web Platform Features]({{< ref "frame-counting.md" >}}) (or a combination of them)-- [Hardware Limitations]({{< ref "../docs/attacks/timing-attacks/connection-pool.md" >}})+> Does a query *?query=existent* return HTTP200 status code while *?query=non-existent* does not?" -# About the Wiki+The latter oracle could be formed from an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) XS-Leak and which could be abused by attackers to infer information about the user. -This Wiki contains an introduction to XS-Leaks enriched with in-depth information. A reader is expected to be able to understand the topic, discover internals of some attacks and how to defend an application against them.-This Wiki aggregates most of the XS-Leaks described and discovered in the wild but frequently new ones emerge. Please feel free to help this Wiki grow and be up to date by contributing to the [XS-Leaks Github Repository](https://github.com/xsleaks/wiki).++Browsers provide tons of different APIs that while well-intentioned, can end up leaking small amounts of information in an unintended way.++## Example++Websites are generally not allowed to access data on other websites. For example, *evil.com* is forbidden from explicitly reading a response from *bank.com*, but *evil.com* can attempt to load a script from *bank.com* since that was originally seen as harmless [^harmless]. However, *evil.com* can also determine whether or not the script successfully loaded.++{{< hint info >}}++Suppose that *bank.com* has an API endpoint that returns data about a user's receipt for a given query.++1. The page *evil.com* could attempt to load the URL *bank.com/my_receipt?q=groceries* as a script.+2. If the user has recently bought groceries, it will load successfully (because of the HTTP200 status code).+3. But if they haven't, it will trigger an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) (because of the HTTP404 status code) that *evil.com* can watch for.
3. But if they haven't, it will trigger an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) (because of the HTTP404 status code) that *evil.com* can listen for.
terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results? -There are several types of XS-Leaks, and they have distinct origins:+In a vulnerable application, the above might be equivalent to: -- [`HTML` APIs]({{< ref "frame-counting.md" >}}) that allow attackers to access information about a page-- [Browser Features]({{< ref "../docs/attacks/historical/stateful-browser-features.md" >}}) which unintendedly introduced XS-Leaks-- [Browser Bugs]({{< ref "../docs/attacks/historical/Content-Type.md#typemustmatch" >}})-- [Inherent Web Platform Features]({{< ref "frame-counting.md" >}}) (or a combination of them)-- [Hardware Limitations]({{< ref "../docs/attacks/timing-attacks/connection-pool.md" >}})+> Does a query *?query=existent* return HTTP200 status code while *?query=non-existent* does not?" -# About the Wiki+The latter oracle could be formed from an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) XS-Leak and which could be abused by attackers to infer information about the user. -This Wiki contains an introduction to XS-Leaks enriched with in-depth information. A reader is expected to be able to understand the topic, discover internals of some attacks and how to defend an application against them.-This Wiki aggregates most of the XS-Leaks described and discovered in the wild but frequently new ones emerge. Please feel free to help this Wiki grow and be up to date by contributing to the [XS-Leaks Github Repository](https://github.com/xsleaks/wiki).++Browsers provide tons of different APIs that while well-intentioned, can end up leaking small amounts of information in an unintended way.++## Example++Websites are generally not allowed to access data on other websites. For example, *evil.com* is forbidden from explicitly reading a response from *bank.com*, but *evil.com* can attempt to load a script from *bank.com* since that was originally seen as harmless [^harmless]. However, *evil.com* can also determine whether or not the script successfully loaded.++{{< hint info >}}++Suppose that *bank.com* has an API endpoint that returns data about a user's receipt for a given query.++1. The page *evil.com* could attempt to load the URL *bank.com/my_receipt?q=groceries* as a script.

I would add that this will cause the browser to attack cookies.

terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results? -There are several types of XS-Leaks, and they have distinct origins:+In a vulnerable application, the above might be equivalent to: -- [`HTML` APIs]({{< ref "frame-counting.md" >}}) that allow attackers to access information about a page-- [Browser Features]({{< ref "../docs/attacks/historical/stateful-browser-features.md" >}}) which unintendedly introduced XS-Leaks-- [Browser Bugs]({{< ref "../docs/attacks/historical/Content-Type.md#typemustmatch" >}})-- [Inherent Web Platform Features]({{< ref "frame-counting.md" >}}) (or a combination of them)-- [Hardware Limitations]({{< ref "../docs/attacks/timing-attacks/connection-pool.md" >}})+> Does a query *?query=existent* return HTTP200 status code while *?query=non-existent* does not?" -# About the Wiki+The latter oracle could be formed from an [Error Event]({{< ref "./docs/attacks/error-events.md" >}}) XS-Leak and which could be abused by attackers to infer information about the user. -This Wiki contains an introduction to XS-Leaks enriched with in-depth information. A reader is expected to be able to understand the topic, discover internals of some attacks and how to defend an application against them.-This Wiki aggregates most of the XS-Leaks described and discovered in the wild but frequently new ones emerge. Please feel free to help this Wiki grow and be up to date by contributing to the [XS-Leaks Github Repository](https://github.com/xsleaks/wiki).++Browsers provide tons of different APIs that while well-intentioned, can end up leaking small amounts of information in an unintended way.
Browsers provide a wide range of different APIs that, while well-intended, can end up leaking small amounts of information cross-origin.
terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users.  -When `attacker.com` makes a request to `bank.com`, the browser will attach user cookies along with the request. The `Same-origin policy` is enforced and stop `attacker.com` from getting the contents of `bank.com`, and the principle of XS-Leaks is to go around this protection to **infer** Private Identifiable Information (PII) by watching the behavior of secondary events/channels (e.g Browser Cache, Request Timing).+These pieces of information usually have a binary form and are called oracles. These oracles usually answer with *YES* and *NO* to cleverly prepared questions. For example, such an oracle could be asked: -Leaked information is **always** application dependent, varies depending on the features of the application and how it behaves in specific situations.+> Does a word *existent* exist in the search results?

This is unclear. Do you mean

Does the word "existent" exists in the search results?

If not I didn't understand this bit.

terjanq

comment created time in 7 days

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF]. -## Why are XS-Leaks Hard to fix? -- The cause of most XS-Leaks is **inherent to the design of the web**. Applications can be vulnerable to some XS-Leaks without explicitly introducing them.-- Unlike common vulnerabilities such as `XSS`, with very specific taxonomies and attack consequences, XS-Leaks are an aggregation of several attack vectors with different implications.-- XS-Leaks are application behavior dependent, so their consequences can't be generalized.-- Mitigating one XS-Leak can be irrelevant if all the others are possible to exploit. To be effective against most of them, applications must combine different types of [Defense Mechanisms]({{< ref "../docs/defenses/" >}}).+## The principle of an XS-Leak -## The Principle of XS-Leaks+Websites interacting with each other is core to the behavior of the web. Browsers provide a wide variety of interfaces for such interaction between different web applications. These interfaces have different security measures built on top to try to constrain websites behavior (e.g. the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). XS-Leaks take advantage of small pieces of information that can leak during these interactions in order to infer sensitive information about users. 

At the end when you say "about users" I would say about "users, operating systems or networks".

terjanq

comment created time in 7 days

PullRequestReviewEvent

Pull request review commentxsleaks/wiki

Improved the text for a front page of the wiki

 type: docs bookToc: false --- -# What are XS-Leaks?+# XS-Leaks Wiki+## Overview -Cross-Site Leaks are a class of vulnerabilities that have been present on the web for a long time, gaining new attention at the end of 2018 when a formal name was assigned and a [repository](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels) of some of them was created. These vulnerabilities are Browser Side-channel Attacks and most of them exploit behaviors **inherent to the design of the web**, which increases the complexity around their mitigation.+Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF, XSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by utilising a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to the browser despite an accepted risk of cross-site leaks [^STTF].

I think adding (CSRF, XSRF) to a spelled-out and capitalized name is a bit redundant. I would personally either remove the acronyms or pick one and stick with it.

Utilising is not a common word, I think exploiting is much more used.

Do you have data on the bit where you say the features being exploited are often maintained to preserve backward compatibility? If not I would just rephrase it to say that they might be. (I was surprised by the "often").

Cross-Site Leaks (XS-Leaks, XSLeaks) are a class of vulnerabilities derived from browser side-channel techniques [^side-channel]. These are similar to Cross-Site Request Forgery [^csrf] (CSRF) techniques but instead of allowing other websites to take actions on behalf of a user, they can be used to infer information about them. This is done by exploiting a variety of features built into browsers which are often maintained to preserve backwards compatibility. Though, sometimes new features are added to browsers regardless the introduction of potential cross-site leaks [^STTF] as the benefits are considered to overweight the downsides.
terjanq

comment created time in 7 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha 5f2eff00b0086b7c3a9cb017c2d828b455e054b2

Refactor the tests that use the recover function to check for != nil i.e. recover actually recovered from a panic

view details

push time in 9 days

PR merged google/go-safeweb

Reviewers
Refactor the tests that use the recover() function hacktoberfest-accepted internal cleanup

Modified the tests that ensure a panic was triggered from

defer func() {
	if r := recover(); r == nil {
		t.Errorf(`... expected panic`)
	}
}()

to

defer func() {
	if r := recover(); r != nil {
		return
	}
	t.Errorf(`... expected panic`)
}()

i.e. they now check the recover() function recovered from a panic instead of checking it didn't.

+48 -32

0 comment

6 changed files

maramihali

pr closed time in 9 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha 99fac467e2ff942ba24dae45499d79fcb1592044

Extended the Interceptor interface with the OnError method that will run when an error occurs

view details

Mara Mihali

commit sha 21a1a8a0e1b2c81cdf2ff24e8a751cc55813eb22

Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called

view details

Mara Mihali

commit sha 0fdaf2caa7673b45f9a247b46bd16c7aae6c76a4

Fix typo

view details

Mara Mihali

commit sha 51efe94e782a30555cad7015be7740c5ae08d401

Add the OnError method to the COOP plugin

view details

Mara Mihali

commit sha 916a81796476aac5af50a93edfdd0c82d5612644

Added TODO about whether OnError should be notified the Commit phase of the interceptor was already called

view details

push time in 9 days

PR merged google/go-safeweb

Reviewers
Added the OnError method to the safehttp.Interceptor interface enhancement hacktoberfest-accepted

Fixes #201

The OnError method of all interceptors associated with a handler will be called in safehttp.ResponseWriter.WriteError before the error response is actually written.

+184 -12

1 comment

13 changed files

maramihali

pr closed time in 9 days

issue closedgoogle/go-safeweb

Extend the Interceptor interface with an OnError method

This will run when the safehttp.ResponseWriter.WriteError method is called, before the error response is actually written, similarly to how Commit is run in the other methods for writing to the ResponseWriter.

closed time in 9 days

maramihali

Pull request review commentgoogle/go-safeweb

Added the OnError method to the safehttp.Interceptor interface

 type Interceptor interface { 	// is written to the ResponseWriter, then the Commit phases from the 	// remaining interceptors won't execute. 	Commit(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result++	// OnError runs when ResponseWriter.WriteError is called, before the+	// actual error response is written. An attempt to write to the+	// ResponseWriter in this phase will result in an irrecoverable error.+	OnError(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result

We should revisit this in the future. Definitely not a blocker.

maramihali

comment created time in 9 days

PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+)++var statePreservingMethods = map[string]bool{+	safehttp.MethodGet:     true,+	safehttp.MethodHead:    true,+	safehttp.MethodOptions: true,+}++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the seesion cookie that holds the XSRF+	// token. In order to prevent collisions when multiple applications share+	// the same domain or subdomain, each application should set a unique name+	// for the cookie.+	TokenCookieName string+	// TokenHeaderName is the name of the HTTP header that also holds the XSRF+	// token.+	TokenHeaderName string+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request Forgery attacks.+//+// It will check for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	c, err := r.Cookie(it.TokenCookieName)+	if err != nil {+		if !statePreservingMethods[r.Method()] {+			return w.WriteError(safehttp.StatusForbidden)+		}+		return safehttp.NotWritten()+	}++	tok := r.Header.Get(it.TokenHeaderName)+	if tok == "" || tok != c.Value() {+		return w.WriteError(safehttp.StatusUnauthorized)+	}++	return safehttp.NotWritten()+}++func (it *Interceptor) addAngularTokenCookie(w *safehttp.ResponseWriter) error {+	tok := make([]byte, 20)+	if _, err := rand.Read(tok); err != nil {+		return fmt.Errorf("crypto/rand.Read: %v", err)+	}+	c := safehttp.NewCookie(it.TokenCookieName, base64.StdEncoding.EncodeToString(tok))++	c.SetSameSite(safehttp.SameSiteStrictMode)+	c.SetPath("/")+	// Set the duration of the token cookie to 24 hours.+	c.SetMaxAge(86400)+	// Needed in order to make the cookie accessible by JavaScript+	// running on the user's domain.+	c.DisableHTTPOnly()++	if err := w.SetCookie(c); err != nil {+		return err+	}+	return nil+}++// Commit generates a cryptographically random cookie on the first state
// Commit generates a cryptographically secure random cookie on the first state
maramihali

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+)++var statePreservingMethods = map[string]bool{+	safehttp.MethodGet:     true,+	safehttp.MethodHead:    true,+	safehttp.MethodOptions: true,+}++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the seesion cookie that holds the XSRF+	// token. In order to prevent collisions when multiple applications share+	// the same domain or subdomain, each application should set a unique name+	// for the cookie.+	TokenCookieName string+	// TokenHeaderName is the name of the HTTP header that also holds the XSRF+	// token.+	TokenHeaderName string+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request Forgery attacks.+//+// It will check for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	c, err := r.Cookie(it.TokenCookieName)+	if err != nil {+		if !statePreservingMethods[r.Method()] {+			return w.WriteError(safehttp.StatusForbidden)+		}+		return safehttp.NotWritten()+	}

I think the first thing you should do is the check for statePreservingMethod, and only if it is not state preserving you look for the Cookie.

	if statePreservingMethods[r.Method()] {
		return safehttp.NotWritten()
	}
	c, err := r.Cookie(it.TokenCookieName)
	if err != nil || c.Value() == ""{
		return w.WriteError(safehttp.StatusForbidden)
	}
maramihali

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+)++var statePreservingMethods = map[string]bool{+	safehttp.MethodGet:     true,+	safehttp.MethodHead:    true,+	safehttp.MethodOptions: true,+}++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the seesion cookie that holds the XSRF+	// token. In order to prevent collisions when multiple applications share+	// the same domain or subdomain, each application should set a unique name+	// for the cookie.+	TokenCookieName string+	// TokenHeaderName is the name of the HTTP header that also holds the XSRF+	// token.+	TokenHeaderName string+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request Forgery attacks.+//+// It will check for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	c, err := r.Cookie(it.TokenCookieName)+	if err != nil {+		if !statePreservingMethods[r.Method()] {+			return w.WriteError(safehttp.StatusForbidden)+		}+		return safehttp.NotWritten()+	}++	tok := r.Header.Get(it.TokenHeaderName)+	if tok == "" || tok != c.Value() {+		return w.WriteError(safehttp.StatusUnauthorized)+	}++	return safehttp.NotWritten()+}++func (it *Interceptor) addAngularTokenCookie(w *safehttp.ResponseWriter) error {+	tok := make([]byte, 20)+	if _, err := rand.Read(tok); err != nil {+		return fmt.Errorf("crypto/rand.Read: %v", err)+	}+	c := safehttp.NewCookie(it.TokenCookieName, base64.StdEncoding.EncodeToString(tok))++	c.SetSameSite(safehttp.SameSiteStrictMode)+	c.SetPath("/")+	// Set the duration of the token cookie to 24 hours.+	c.SetMaxAge(86400)+	// Needed in order to make the cookie accessible by JavaScript+	// running on the user's domain.+	c.DisableHTTPOnly()++	if err := w.SetCookie(c); err != nil {+		return err+	}+	return nil+}++// Commit generates a cryptographically random cookie on the first state+// preserving request (GET, HEAD or OPTION) and sets it in the response. On+// every subsequent request the cookie is expected alongside a header that+// matches its value.+func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {++	_, err := r.Cookie(it.TokenCookieName)+	if err == nil {+		return safehttp.NotWritten()+	}
	c, err := r.Cookie(it.TokenCookieName)
	if err == nil && c.Value() != "" {

This condition should change the one we have in the checker.

maramihali

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+	"github.com/google/go-cmp/cmp"+	"github.com/google/go-safeweb/safehttp"+	"github.com/google/go-safeweb/safehttp/safehttptest"+	"strings"+	"testing"+)++const (+	cookieName = "XSRF-TOKEN"+	headerName = "X-XSRF-TOKEN"+)++func TestAngularAddCookie(t *testing.T) {+	req := safehttptest.NewRequest(safehttp.MethodGet, "/", nil)+	rec := safehttptest.NewResponseRecorder()+	i := &Interceptor{+		TokenCookieName: cookieName,+		TokenHeaderName: headerName,+	}+	i.Commit(rec.ResponseWriter, req, nil, nil)++	if got, want := rec.Status(), safehttp.StatusOK; got != want {+		t.Errorf("rec.Status(): got %v, want %v", got, want)+	}++	tokCookieDefaults := "Path=/; Max-Age=86400; Secure; SameSite=Strict"+	got := map[string][]string(rec.Header())["Set-Cookie"][0]+	if !strings.Contains(got, i.TokenCookieName) {+		t.Errorf("Set-Cookie header: %s not present", i.TokenCookieName)+	}+	if !strings.Contains(got, tokCookieDefaults) {+		t.Errorf("Set-Cookie header: got %s, want defaults %s", got, tokCookieDefaults)+	}++	if got, want := rec.Body(), ""; got != want {+		t.Errorf("rec.Body(): got %q want %q", got, want)+	}+}++func TestAngularPostProtection(t *testing.T) {+	tests := []struct {+		name       string+		req        *safehttp.IncomingRequest+		wantStatus safehttp.StatusCode+		wantHeader map[string][]string+		wantBody   string+	}{+		{+			name: "Same cookie and header",+			req: func() *safehttp.IncomingRequest {+				req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+				req.Header.Set("Cookie", cookieName+"="+"1234")+				req.Header.Set(headerName, "1234")+				return req+			}(),+			wantStatus: safehttp.StatusOK,+			wantHeader: map[string][]string{},+			wantBody:   "",+		},+		{+			name: "Different cookie and header",+			req: func() *safehttp.IncomingRequest {+				req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+				req.Header.Set("Cookie", cookieName+"="+"5768")+				req.Header.Set(headerName, "1234")+				return req+			}(),+			wantStatus: safehttp.StatusUnauthorized,+			wantHeader: map[string][]string{+				"Content-Type":           {"text/plain; charset=utf-8"},+				"X-Content-Type-Options": {"nosniff"},+			},+			wantBody: "Unauthorized\n",+		},+		{+			name: "Missing header",+			req: func() *safehttp.IncomingRequest {+				req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+				req.Header.Set("Cookie", cookieName+"="+"1234")+				return req+			}(),+			wantStatus: safehttp.StatusUnauthorized,+			wantHeader: map[string][]string{+				"Content-Type":           {"text/plain; charset=utf-8"},+				"X-Content-Type-Options": {"nosniff"},+			},+			wantBody: "Unauthorized\n",+		},+		{+			name: "Missing cookie",+			req: func() *safehttp.IncomingRequest {+				req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+				req.Header.Set(headerName, "1234")+				return req+			}(),+			wantStatus: safehttp.StatusForbidden,+			wantHeader: map[string][]string{+				"Content-Type":           {"text/plain; charset=utf-8"},+				"X-Content-Type-Options": {"nosniff"},+			},+			wantBody: "Forbidden\n",

These assertions feel like we are testing the framework rather than just the plugin. I would personally just test for status here. If you want to be thorough maybe also body, but the header check I think should only be a "I added a cookie or not", which would allow you to merge this test with the one above.

maramihali

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf

Since we expect to have a couple of these packages I would call them xsrf/angular and xsrf/html in the directory tree, and I would name the package xsrf* not *xsrf.

Moreover since we are creating a new directory you could create a common package with exported helpers (e.g. a function that checks if a method is state-preserving)

The final directory tree that I envision is:

plugins/
  xsrf/
    angular/
      package_xsrfangular.go
    vanilla/
      package_xsrfhtml.go
    package_xsrf.go

and the common code between the two would go in the xsrf package. The fact that we didn't find a proper abstraction doesn't mean we have to duplicate code :)

maramihali

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+)++var statePreservingMethods = map[string]bool{+	safehttp.MethodGet:     true,+	safehttp.MethodHead:    true,+	safehttp.MethodOptions: true,+}++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the seesion cookie that holds the XSRF+	// token. In order to prevent collisions when multiple applications share+	// the same domain or subdomain, each application should set a unique name+	// for the cookie.+	TokenCookieName string+	// TokenHeaderName is the name of the HTTP header that also holds the XSRF+	// token.+	TokenHeaderName string+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request Forgery attacks.

This is given by the framework, I would remove this and just say what Before does.

maramihali

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+)++var statePreservingMethods = map[string]bool{+	safehttp.MethodGet:     true,+	safehttp.MethodHead:    true,+	safehttp.MethodOptions: true,+}++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the seesion cookie that holds the XSRF+	// token. In order to prevent collisions when multiple applications share+	// the same domain or subdomain, each application should set a unique name+	// for the cookie.+	TokenCookieName string+	// TokenHeaderName is the name of the HTTP header that also holds the XSRF+	// token.+	TokenHeaderName string+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request Forgery attacks.+//+// It will check for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	c, err := r.Cookie(it.TokenCookieName)+	if err != nil {+		if !statePreservingMethods[r.Method()] {+			return w.WriteError(safehttp.StatusForbidden)+		}+		return safehttp.NotWritten()+	}++	tok := r.Header.Get(it.TokenHeaderName)+	if tok == "" || tok != c.Value() {

If you accept my code above you can remove the tok=="" bit.

maramihali

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

Created a XSRF plugin for Angular

+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// 	https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+	"crypto/rand"+	"encoding/base64"+	"fmt"+	"github.com/google/go-safeweb/safehttp"+)++var statePreservingMethods = map[string]bool{+	safehttp.MethodGet:     true,+	safehttp.MethodHead:    true,+	safehttp.MethodOptions: true,+}++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+	// TokenCookieName is the name of the seesion cookie that holds the XSRF+	// token. In order to prevent collisions when multiple applications share+	// the same domain or subdomain, each application should set a unique name+	// for the cookie.+	TokenCookieName string+	// TokenHeaderName is the name of the HTTP header that also holds the XSRF+	// token.
	// TokenHeaderName is the name of the HTTP header that holds the XSRF token.
maramihali

comment created time in 9 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventgoogle/go-safeweb

empijei

commit sha 7997723d27f7adf2c17e40ac02bad542cbd99715

coop: internal refactor

view details

push time in 9 days

PR merged google/go-safeweb

coop: internal refactor hacktoberfest-accepted internal cleanup plugin

As discussed with @kele this provides better separation between data and code.

+18 -13

0 comment

1 changed file

empijei

pr closed time in 9 days

Pull request review commentgoogle/go-safeweb

coop: internal refactor

 func (p Policy) String() string { 	return string(p.Mode) + `; report-to "` + p.ReportingGroup + `"` } -// NewInterceptor constructs an interceptor that applies the given policies.-func NewInterceptor(policies ...Policy) Interceptor {-	var rep []string-	var enf []string+type serializedPolicies struct {+	rep []string+	enf []string+}++func serializePolicies(policies ...Policy) serializedPolicies {+	var s serializedPolicies 	for _, p := range policies { 		if p.ReportOnly {-			rep = append(rep, p.String())+			s.rep = append(s.rep, p.String()) 		} else {-			enf = append(enf, p.String())+			s.enf = append(s.enf, p.String()) 		} 	}-	return Interceptor{rep: rep, enf: enf}+	return s+}++// NewInterceptor constructs an interceptor that applies the given policies.+func NewInterceptor(policies ...Policy) Interceptor {

This package exports other types than just Interceptor, so I just added the extra bit to make sure ppl did not confuse this for an overrider or smth else.

empijei

comment created time in 9 days

PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

ResponseWriter and Dispatcher now have a single Write method

 func TestDefaultDispatcherValidResponse(t *testing.T) { 						Parse("<h1>{{ . }}</h1>"))) 				var data interface{} 				data = "This is an actual heading, though."-				resp := safehttp.TemplateResponse{-					Template: &t,-					Data:     &data,-				}-				return d.ExecuteTemplate(w, resp)+				return d.Write(w, safehttp.NewTemplateResponse(t, data, nil))

NewTemplateResponse will be used a lot and it seems quite a long name to use.

What do you think about just Tpl or TplResponse or Template or other ways to shorten it?

maramihali

comment created time in 9 days

PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

ResponseWriter and Dispatcher now have a single Write method

 type TemplateResponse struct { // NoContentResponse is sent to the commit phase when it's initiated from // ResponseWriter.NoContent. type NoContentResponse struct{}++// NewJSONResponse creates a JSON response from a data object. This should be a+// valid JSON object that can be serialised and written to the+// http.ResponseWriter using a JSON encoder.+func NewJSONResponse(data interface{}) JSONResponse {+	return JSONResponse{Data: data}+}++// NewTemplateResponse creates a TemplateResponse from a Template, its data and+// the name to function mappings. These will be pre-processed and then written+// to the http.ResponseWriter, if deemed safe. If the funcMap is non-nil, its+// elements will override the existing name to function mappings in the+// template. An attempt to define a new name to function mapping that is not+// already in the template will result in a panic.+func NewTemplateResponse(t Template, data interface{}, funcMap map[string]interface{}) TemplateResponse {+	return TemplateResponse{+		Template: &t,+		Data:     &data,

Why are you taking a reference here?

maramihali

comment created time in 9 days

PullRequestReviewEvent

issue commentgolang/go

net/http: SameSiteDefaultMode adds an incorrect 'Set-Cookie' attribute

My 2c on this:

  • Supporting older browsers is fine as long as we do not damage functionality in some other browsers. The behavior described by @euank sounds like a nightmare to debug and relies too heavily on web platform quirks

using the 'Default' value to get 'Lax' on some browsers (newer chrome/ff I think), 'None' on other browsers (older chrome/ff), 'strict' on other browsers (apparently ios), and silently drop the cookie on yet other browsers (some older chrome/android).

  • The http package tries to implement the spec, and when that changes we have to move forward if the vast majority of browsers does. I don't want users to set the default value expecting to increase security when instead they will get absolutely no benefit.
  • WRT deprecation, we could indeed deprecate SameSiteDefault, wanna create a CL to add the Deprecated: reason doc line?
  • What would be the value in keeping the old behavior? When you say "old iOS Safari", how old are we talking about?
  • Would have you preferred to have "lax" instead of "none" as default? Because if this is the case we would have risked breaking many users, which I'm not willing to commit to.
euank

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Added the OnError method to the safehttp.Interceptor interface

 func TestMuxHandlerReturnsNotWritten(t *testing.T) { 		t.Errorf(`response body got: %q want: ""`, got) 	} }++type onErrorWriteInterceptor struct{}++func (onErrorWriteInterceptor) Before(w *safehttp.ResponseWriter, _ *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+	return w.WriteError(safehttp.StatusBadRequest)+}++func (onErrorWriteInterceptor) Commit(_ *safehttp.ResponseWriter, _ *safehttp.IncomingRequest, _ safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	return safehttp.Result{}+}++func (onErrorWriteInterceptor) OnError(w *safehttp.ResponseWriter, _ *safehttp.IncomingRequest, _ safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	w.Header().Set("header", "bad")+	return w.WriteError(safehttp.StatusInsufficientStorage)+}++func TestMuxInterceptorWriteInOnError(t *testing.T) {+	b := &strings.Builder{}+	rw := safehttptest.NewTestResponseWriter(b)++	req := httptest.NewRequest(safehttp.MethodGet, "http://foo.com/bar", nil)++	mb := &safehttp.ServeMuxConfig{}+	mb.Intercept(onErrorWriteInterceptor{})++	registeredHandler := safehttp.HandlerFunc(func(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {+		return w.Write(safehtml.HTMLEscaped("<h1>Hello World!</h1>"))+	})+	mb.Handle("/bar", safehttp.MethodGet, registeredHandler)++	defer func() {+		if r := recover(); r == nil {+			t.Errorf(`mb.Mux().ServeHTTP(rw, req): expected panic`)+		}+	}()
	defer func() {
		if r := recover(); r != nil {
			return
		}
		t.Errorf(`mb.Mux().ServeHTTP(rw, req): expected panic`)
	}()

SuperNit: Reading a recover with a == nil condition might be surprising for some users.

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Added the OnError method to the safehttp.Interceptor interface

 type Interceptor interface { 	// is written to the ResponseWriter, then the Commit phases from the 	// remaining interceptors won't execute. 	Commit(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result++	// OnError runs when ResponseWriter.WriteError is called, before the+	// actual error response is written. An attempt to write to the+	// ResponseWriter in this phase will result in an irrecoverable error.+	OnError(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result

should we maybe pass something to the interceptor to tell them if we've already called their own Commit?

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Added the OnError method to the safehttp.Interceptor interface

 func (h handler) commitPhase(w *ResponseWriter, resp Response) { 		} 	} }++// errrorPhase calls the OnError phases of all the interceptors associated with

too many "r"?

maramihali

comment created time in 10 days

PullRequestReviewEvent
PullRequestReviewEvent

PR opened google/go-safeweb

coop: internal refactor

As discussed with @kele this provides better separation between data and code.

+18 -13

0 comment

1 changed file

pr created time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	c            Checker+	i            Injector }  var _ safehttp.Interceptor = &Interceptor{} -type tokenCtxKey struct{}--// Token extracts the XSRF token from the incoming request. If it is not-// present, it returns a non-nil error.-func Token(r *safehttp.IncomingRequest) (string, error) {-	tok := r.Context().Value(tokenCtxKey{})-	if tok == nil {-		return "", errors.New("xsrf token not found")+func New(key string, c Checker, i Injector) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c:            c,+		i:            i, 	}-	return tok.(string), nil } -func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {-	buf := make([]byte, 20)-	if _, err := rand.Read(buf); err != nil {-		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+func Default(key string) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c: defaultChecker{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+			tokenKey:     "xsrf-token",+		},+		i: defaultInjector{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+		}, 	}+} -	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))-	c.SetSameSite(safehttp.SameSiteStrictMode)-	if err := w.SetCookie(c); err != nil {-		return nil, err+func Angular(cookieName, headerName string) Interceptor {+	return Interceptor{+		c: angularChecker{+			tokenCookieName: cookieName,+			tokenHeaderName: headerName,+		},+		i: angularInjector{+			tokenCookieName: cookieName,+		}, 	}-	return c, nil } -// Before should be executed before directing the safehttp.IncomingRequest to-// the handler to ensure it is not part of a Cross-Site Request-// Forgery attack.-//-// On first user visit through a state preserving request (GET, HEAD or-// OPTIONS), a nonce-based cookie will be set in the response as a way to-// distinguish between users and prevent pre-login XSRF attacks. The cookie will-// be used in the token generation and verification algorithm and is expected to-// be present in all subsequent incoming requests.-//-// For every authorized request, the interceptor will also generate a-// cryptographically-safe XSRF token using the appKey, the cookie and the path-// visited. This can be later extracted using Token and should be injected as a-// hidden input field in HTML forms.-//-// In case of state changing requests (all except GET, HEAD and OPTIONS), the-// interceptor checks for the presence of the XSRF token in the request body-// (expected to have been injected) and validates it. func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {-	needsValidation := !statePreservingMethods[r.Method()]-	cookieID, err := r.Cookie(cookieIDKey)+	if statePreservingMethods[r.Method()] {+		return safehttp.NotWritten()+	}+	tok, userID, actionID, code := it.c.Retrieve(r)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	code = it.c.Validate(tok, userID, actionID)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	return safehttp.NotWritten()

@kele I think we might have overdone the abstraction bit a tad too much.

All of this CL and all of this work is just to allow us to not repeat these 12 lines?

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	c            Checker+	i            Injector }  var _ safehttp.Interceptor = &Interceptor{} -type tokenCtxKey struct{}--// Token extracts the XSRF token from the incoming request. If it is not-// present, it returns a non-nil error.-func Token(r *safehttp.IncomingRequest) (string, error) {-	tok := r.Context().Value(tokenCtxKey{})-	if tok == nil {-		return "", errors.New("xsrf token not found")+func New(key string, c Checker, i Injector) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c:            c,+		i:            i, 	}-	return tok.(string), nil } -func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {-	buf := make([]byte, 20)-	if _, err := rand.Read(buf); err != nil {-		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+func Default(key string) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c: defaultChecker{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+			tokenKey:     "xsrf-token",+		},+		i: defaultInjector{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+		}, 	}+} -	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))-	c.SetSameSite(safehttp.SameSiteStrictMode)-	if err := w.SetCookie(c); err != nil {-		return nil, err+func Angular(cookieName, headerName string) Interceptor {+	return Interceptor{+		c: angularChecker{+			tokenCookieName: cookieName,+			tokenHeaderName: headerName,+		},+		i: angularInjector{+			tokenCookieName: cookieName,+		}, 	}-	return c, nil } -// Before should be executed before directing the safehttp.IncomingRequest to-// the handler to ensure it is not part of a Cross-Site Request-// Forgery attack.-//-// On first user visit through a state preserving request (GET, HEAD or-// OPTIONS), a nonce-based cookie will be set in the response as a way to-// distinguish between users and prevent pre-login XSRF attacks. The cookie will-// be used in the token generation and verification algorithm and is expected to-// be present in all subsequent incoming requests.-//-// For every authorized request, the interceptor will also generate a-// cryptographically-safe XSRF token using the appKey, the cookie and the path-// visited. This can be later extracted using Token and should be injected as a-// hidden input field in HTML forms.-//-// In case of state changing requests (all except GET, HEAD and OPTIONS), the-// interceptor checks for the presence of the XSRF token in the request body-// (expected to have been injected) and validates it. func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {-	needsValidation := !statePreservingMethods[r.Method()]-	cookieID, err := r.Cookie(cookieIDKey)+	if statePreservingMethods[r.Method()] {+		return safehttp.NotWritten()+	}+	tok, userID, actionID, code := it.c.Retrieve(r)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	code = it.c.Validate(tok, userID, actionID)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	return safehttp.NotWritten()+}++func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	err := it.i.Inject(resp, w, r) 	if err != nil {-		if needsValidation {-			return w.WriteError(safehttp.StatusForbidden)-		}-		cookieID, err = addCookieID(w)-		if err != nil {-			// An error is returned when the plugin fails to Set the Set-Cookie-			// header in the response writer as this is a server misconfiguration.-			return w.WriteError(safehttp.StatusInternalServerError)-		}+		return w.WriteError(safehttp.StatusInternalServerError)+	}+	return safehttp.Result{}+}++// Checker decides whether a safehttp.IncomingRequest should be allowed to pass+// or it's part of a Cross-Site Request Forgery (XSRF) attack.+type Checker interface {+	// Retrieve returns the information necessary to verify the validity of+	// a XSRF token.+	Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode)+	// Validate validates the token contained in the request, optionally using+	// the user session and action the user is taking.+	Validate(token string, userID string, actionID string) safehttp.StatusCode+}++type Injector interface {+	// Inject adds the protection necessary to the safehttp.Response and+	// safehttp.ResponseWriter so that subsequent request can be deemed safe.+	Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error

Nite: for sake of coherence let's preserve the order of the Commit phase: w, r, resp

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	c            Checker+	i            Injector }  var _ safehttp.Interceptor = &Interceptor{} -type tokenCtxKey struct{}--// Token extracts the XSRF token from the incoming request. If it is not-// present, it returns a non-nil error.-func Token(r *safehttp.IncomingRequest) (string, error) {-	tok := r.Context().Value(tokenCtxKey{})-	if tok == nil {-		return "", errors.New("xsrf token not found")+func New(key string, c Checker, i Injector) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c:            c,+		i:            i, 	}-	return tok.(string), nil } -func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {-	buf := make([]byte, 20)-	if _, err := rand.Read(buf); err != nil {-		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+func Default(key string) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c: defaultChecker{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+			tokenKey:     "xsrf-token",+		},+		i: defaultInjector{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+		}, 	}+} -	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))-	c.SetSameSite(safehttp.SameSiteStrictMode)-	if err := w.SetCookie(c); err != nil {-		return nil, err+func Angular(cookieName, headerName string) Interceptor {+	return Interceptor{+		c: angularChecker{+			tokenCookieName: cookieName,+			tokenHeaderName: headerName,+		},+		i: angularInjector{+			tokenCookieName: cookieName,+		}, 	}-	return c, nil } -// Before should be executed before directing the safehttp.IncomingRequest to-// the handler to ensure it is not part of a Cross-Site Request-// Forgery attack.-//-// On first user visit through a state preserving request (GET, HEAD or-// OPTIONS), a nonce-based cookie will be set in the response as a way to-// distinguish between users and prevent pre-login XSRF attacks. The cookie will-// be used in the token generation and verification algorithm and is expected to-// be present in all subsequent incoming requests.-//-// For every authorized request, the interceptor will also generate a-// cryptographically-safe XSRF token using the appKey, the cookie and the path-// visited. This can be later extracted using Token and should be injected as a-// hidden input field in HTML forms.-//-// In case of state changing requests (all except GET, HEAD and OPTIONS), the-// interceptor checks for the presence of the XSRF token in the request body-// (expected to have been injected) and validates it. func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {-	needsValidation := !statePreservingMethods[r.Method()]-	cookieID, err := r.Cookie(cookieIDKey)+	if statePreservingMethods[r.Method()] {+		return safehttp.NotWritten()+	}+	tok, userID, actionID, code := it.c.Retrieve(r)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	code = it.c.Validate(tok, userID, actionID)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	return safehttp.NotWritten()+}++func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	err := it.i.Inject(resp, w, r) 	if err != nil {-		if needsValidation {-			return w.WriteError(safehttp.StatusForbidden)-		}-		cookieID, err = addCookieID(w)-		if err != nil {-			// An error is returned when the plugin fails to Set the Set-Cookie-			// header in the response writer as this is a server misconfiguration.-			return w.WriteError(safehttp.StatusInternalServerError)-		}+		return w.WriteError(safehttp.StatusInternalServerError)+	}+	return safehttp.Result{}+}++// Checker decides whether a safehttp.IncomingRequest should be allowed to pass+// or it's part of a Cross-Site Request Forgery (XSRF) attack.+type Checker interface {+	// Retrieve returns the information necessary to verify the validity of+	// a XSRF token.+	Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode)+	// Validate validates the token contained in the request, optionally using+	// the user session and action the user is taking.+	Validate(token string, userID string, actionID string) safehttp.StatusCode+}++type Injector interface {+	// Inject adds the protection necessary to the safehttp.Response and+	// safehttp.ResponseWriter so that subsequent request can be deemed safe.+	Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error+}++type defaultChecker struct {+	secretAppKey string+	cookieIDKey  string+	tokenKey     string+}++func (c defaultChecker) Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode) {+	cookie, err := r.Cookie(c.cookieIDKey)+	if err != nil {+		code = safehttp.StatusForbidden+		return 	}+	userID = cookie.Value() -	actionID := r.URL.Path()-	if needsValidation {-		f, err := r.PostForm()+	f, err := r.PostForm()+	if err != nil {+		// We fallback to checking whether the form is multipart. Both types+		// are valid in an incoming request as long as the XSRF token is+		// present.+		mf, err := r.MultipartForm(32 << 20) 		if err != nil {-			// We fallback to checking whether the form is multipart. Both types-			// are valid in an incoming request as long as the XSRF token is-			// present.-			mf, err := r.MultipartForm(32 << 20)-			if err != nil {-				return w.WriteError(safehttp.StatusBadRequest)-			}-			f = &mf.Form+			code = safehttp.StatusBadRequest+			return 		}+		f = &mf.Form+	}++	token = f.String(c.tokenKey, "")+	if f.Err() != nil || token == "" {+		code = safehttp.StatusUnauthorized+		return+	} -		tok := f.String(TokenKey, "")-		if f.Err() != nil || tok == "" {-			return w.WriteError(safehttp.StatusUnauthorized)+	actionID = r.URL.Path()+	return+}++func (c defaultChecker) Validate(token, userID, actionID string) safehttp.StatusCode {+	if !xsrftoken.Valid(token, c.secretAppKey, userID, actionID) {+		return safehttp.StatusForbidden+	}+	return safehttp.StatusOK+}++type defaultInjector struct {+	cookieIDKey  string+	secretAppKey string+}++func (i defaultInjector) Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error {+	c, err := r.Cookie(i.cookieIDKey)+	if err != nil {+		buf := make([]byte, 20)+		if _, err := rand.Read(buf); err != nil {+			return fmt.Errorf("crypto/rand.Read: %v", err) 		}+		c = safehttp.NewCookie(i.cookieIDKey, base64.StdEncoding.EncodeToString(buf))+		c.SetSameSite(safehttp.SameSiteStrictMode) -		if ok := xsrftoken.Valid(tok, it.SecretAppKey, cookieID.Value(), actionID); !ok {-			return w.WriteError(safehttp.StatusForbidden)+		if err := w.SetCookie(c); err != nil {+			return err 		} 	} -	tok := xsrftoken.Generate(it.SecretAppKey, cookieID.Value(), actionID)-	r.SetContext(context.WithValue(r.Context(), tokenCtxKey{}, tok))-	return safehttp.NotWritten()-}+	tok := xsrftoken.Generate(i.secretAppKey, c.Value(), r.URL.Path()) -// Commit adds the XSRF token corresponding to the safehttp.TemplateResponse-// with key "XSRFToken". The token corresponds to the user information found in-// the request.-func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result { 	tmplResp, ok := resp.(safehttp.TemplateResponse) 	if !ok {-		return safehttp.NotWritten()-	}--	tok, err := Token(r)-	if err != nil {-		// The token should have been added in the Before stage and if that is-		// not the case, a server misconfiguration occured.-		return w.WriteError(safehttp.StatusInternalServerError)+		return nil 	}  	// TODO(maramihali@): Change the key when function names are exported by 	// htmlinject 	// TODO: what should happen if the XSRFToken key is not present in the 	// tr.FuncMap? 	tmplResp.FuncMap["XSRFToken"] = func() string { return tok }-	return safehttp.NotWritten()+	return nil+}++type angularChecker struct {+	tokenCookieName string+	tokenHeaderName string+}++func (c angularChecker) Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode) {+	cookie, err := r.Cookie(c.tokenCookieName)+	if err != nil {+		code = safehttp.StatusForbidden+		return+	}+	token = r.Header.Get(c.tokenHeaderName)+	if token == "" || token != cookie.Value() {+		code = safehttp.StatusUnauthorized+		return+	}+	return+}++func (c angularChecker) Validate(_, _, _ string) safehttp.StatusCode {+	return safehttp.StatusOK+}

I know we already discussed this, but maybe it is worth adding a comment on to the why we do not need to re-validate this.

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	c            Checker+	i            Injector }  var _ safehttp.Interceptor = &Interceptor{} -type tokenCtxKey struct{}--// Token extracts the XSRF token from the incoming request. If it is not-// present, it returns a non-nil error.-func Token(r *safehttp.IncomingRequest) (string, error) {-	tok := r.Context().Value(tokenCtxKey{})-	if tok == nil {-		return "", errors.New("xsrf token not found")+func New(key string, c Checker, i Injector) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c:            c,+		i:            i, 	}-	return tok.(string), nil } -func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {-	buf := make([]byte, 20)-	if _, err := rand.Read(buf); err != nil {-		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+func Default(key string) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c: defaultChecker{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+			tokenKey:     "xsrf-token",+		},+		i: defaultInjector{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+		}, 	}+} -	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))-	c.SetSameSite(safehttp.SameSiteStrictMode)-	if err := w.SetCookie(c); err != nil {-		return nil, err+func Angular(cookieName, headerName string) Interceptor {+	return Interceptor{+		c: angularChecker{+			tokenCookieName: cookieName,+			tokenHeaderName: headerName,+		},+		i: angularInjector{+			tokenCookieName: cookieName,+		}, 	}-	return c, nil } -// Before should be executed before directing the safehttp.IncomingRequest to-// the handler to ensure it is not part of a Cross-Site Request-// Forgery attack.-//-// On first user visit through a state preserving request (GET, HEAD or-// OPTIONS), a nonce-based cookie will be set in the response as a way to-// distinguish between users and prevent pre-login XSRF attacks. The cookie will-// be used in the token generation and verification algorithm and is expected to-// be present in all subsequent incoming requests.-//-// For every authorized request, the interceptor will also generate a-// cryptographically-safe XSRF token using the appKey, the cookie and the path-// visited. This can be later extracted using Token and should be injected as a-// hidden input field in HTML forms.-//-// In case of state changing requests (all except GET, HEAD and OPTIONS), the-// interceptor checks for the presence of the XSRF token in the request body-// (expected to have been injected) and validates it. func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {-	needsValidation := !statePreservingMethods[r.Method()]-	cookieID, err := r.Cookie(cookieIDKey)+	if statePreservingMethods[r.Method()] {+		return safehttp.NotWritten()+	}+	tok, userID, actionID, code := it.c.Retrieve(r)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	code = it.c.Validate(tok, userID, actionID)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	return safehttp.NotWritten()+}++func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	err := it.i.Inject(resp, w, r) 	if err != nil {-		if needsValidation {-			return w.WriteError(safehttp.StatusForbidden)-		}-		cookieID, err = addCookieID(w)-		if err != nil {-			// An error is returned when the plugin fails to Set the Set-Cookie-			// header in the response writer as this is a server misconfiguration.-			return w.WriteError(safehttp.StatusInternalServerError)-		}+		return w.WriteError(safehttp.StatusInternalServerError)+	}+	return safehttp.Result{}+}++// Checker decides whether a safehttp.IncomingRequest should be allowed to pass+// or it's part of a Cross-Site Request Forgery (XSRF) attack.+type Checker interface {+	// Retrieve returns the information necessary to verify the validity of+	// a XSRF token.+	Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode)+	// Validate validates the token contained in the request, optionally using+	// the user session and action the user is taking.+	Validate(token string, userID string, actionID string) safehttp.StatusCode+}++type Injector interface {+	// Inject adds the protection necessary to the safehttp.Response and+	// safehttp.ResponseWriter so that subsequent request can be deemed safe.+	Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error+}++type defaultChecker struct {+	secretAppKey string+	cookieIDKey  string+	tokenKey     string+}++func (c defaultChecker) Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode) {+	cookie, err := r.Cookie(c.cookieIDKey)+	if err != nil {+		code = safehttp.StatusForbidden+		return 	}+	userID = cookie.Value() -	actionID := r.URL.Path()-	if needsValidation {-		f, err := r.PostForm()+	f, err := r.PostForm()+	if err != nil {+		// We fallback to checking whether the form is multipart. Both types+		// are valid in an incoming request as long as the XSRF token is+		// present.+		mf, err := r.MultipartForm(32 << 20) 		if err != nil {-			// We fallback to checking whether the form is multipart. Both types-			// are valid in an incoming request as long as the XSRF token is-			// present.-			mf, err := r.MultipartForm(32 << 20)-			if err != nil {-				return w.WriteError(safehttp.StatusBadRequest)-			}-			f = &mf.Form+			code = safehttp.StatusBadRequest+			return 		}+		f = &mf.Form+	}++	token = f.String(c.tokenKey, "")+	if f.Err() != nil || token == "" {+		code = safehttp.StatusUnauthorized+		return+	} -		tok := f.String(TokenKey, "")-		if f.Err() != nil || tok == "" {-			return w.WriteError(safehttp.StatusUnauthorized)+	actionID = r.URL.Path()+	return+}++func (c defaultChecker) Validate(token, userID, actionID string) safehttp.StatusCode {+	if !xsrftoken.Valid(token, c.secretAppKey, userID, actionID) {+		return safehttp.StatusForbidden+	}+	return safehttp.StatusOK+}++type defaultInjector struct {+	cookieIDKey  string+	secretAppKey string+}++func (i defaultInjector) Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error {+	c, err := r.Cookie(i.cookieIDKey)+	if err != nil {+		buf := make([]byte, 20)+		if _, err := rand.Read(buf); err != nil {+			return fmt.Errorf("crypto/rand.Read: %v", err) 		}+		c = safehttp.NewCookie(i.cookieIDKey, base64.StdEncoding.EncodeToString(buf))+		c.SetSameSite(safehttp.SameSiteStrictMode) -		if ok := xsrftoken.Valid(tok, it.SecretAppKey, cookieID.Value(), actionID); !ok {-			return w.WriteError(safehttp.StatusForbidden)+		if err := w.SetCookie(c); err != nil {+			return err 		} 	} -	tok := xsrftoken.Generate(it.SecretAppKey, cookieID.Value(), actionID)-	r.SetContext(context.WithValue(r.Context(), tokenCtxKey{}, tok))-	return safehttp.NotWritten()-}+	tok := xsrftoken.Generate(i.secretAppKey, c.Value(), r.URL.Path()) -// Commit adds the XSRF token corresponding to the safehttp.TemplateResponse-// with key "XSRFToken". The token corresponds to the user information found in-// the request.-func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result { 	tmplResp, ok := resp.(safehttp.TemplateResponse) 	if !ok {-		return safehttp.NotWritten()-	}--	tok, err := Token(r)-	if err != nil {-		// The token should have been added in the Before stage and if that is-		// not the case, a server misconfiguration occured.-		return w.WriteError(safehttp.StatusInternalServerError)+		return nil 	}  	// TODO(maramihali@): Change the key when function names are exported by 	// htmlinject 	// TODO: what should happen if the XSRFToken key is not present in the 	// tr.FuncMap? 	tmplResp.FuncMap["XSRFToken"] = func() string { return tok }-	return safehttp.NotWritten()+	return nil+}++type angularChecker struct {+	tokenCookieName string+	tokenHeaderName string+}++func (c angularChecker) Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode) {+	cookie, err := r.Cookie(c.tokenCookieName)+	if err != nil {+		code = safehttp.StatusForbidden+		return+	}+	token = r.Header.Get(c.tokenHeaderName)+	if token == "" || token != cookie.Value() {+		code = safehttp.StatusUnauthorized+		return+	}+	return+}++func (c angularChecker) Validate(_, _, _ string) safehttp.StatusCode {+	return safehttp.StatusOK+}++type angularInjector struct {+	tokenCookieName string+}++func (i angularInjector) Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error {+	c, err := r.Cookie(i.tokenCookieName)+	if err == nil {+		return nil+	}+	tok := make([]byte, 20)+	if _, err := rand.Read(tok); err != nil {+		return fmt.Errorf("crypto/rand.Read: %v", err)+	}+	c = safehttp.NewCookie(i.tokenCookieName, base64.StdEncoding.EncodeToString(tok))++	c.SetSameSite(safehttp.SameSiteStrictMode)+	c.SetPath("/")+	// Set the duration of the token cookie to 24 hours.+	c.SetMaxAge(86400)

We should be coherent and expire the other XSRF tokens in the same amount of time.

I think 24 hours is a bit tight, what about setting this and the other to a week?

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	c            Checker+	i            Injector }  var _ safehttp.Interceptor = &Interceptor{} -type tokenCtxKey struct{}--// Token extracts the XSRF token from the incoming request. If it is not-// present, it returns a non-nil error.-func Token(r *safehttp.IncomingRequest) (string, error) {-	tok := r.Context().Value(tokenCtxKey{})-	if tok == nil {-		return "", errors.New("xsrf token not found")+func New(key string, c Checker, i Injector) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c:            c,+		i:            i, 	}-	return tok.(string), nil } -func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {-	buf := make([]byte, 20)-	if _, err := rand.Read(buf); err != nil {-		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+func Default(key string) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c: defaultChecker{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+			tokenKey:     "xsrf-token",+		},+		i: defaultInjector{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+		}, 	}+} -	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))-	c.SetSameSite(safehttp.SameSiteStrictMode)-	if err := w.SetCookie(c); err != nil {-		return nil, err+func Angular(cookieName, headerName string) Interceptor {+	return Interceptor{+		c: angularChecker{+			tokenCookieName: cookieName,+			tokenHeaderName: headerName,+		},+		i: angularInjector{+			tokenCookieName: cookieName,+		}, 	}-	return c, nil } -// Before should be executed before directing the safehttp.IncomingRequest to-// the handler to ensure it is not part of a Cross-Site Request-// Forgery attack.-//-// On first user visit through a state preserving request (GET, HEAD or-// OPTIONS), a nonce-based cookie will be set in the response as a way to-// distinguish between users and prevent pre-login XSRF attacks. The cookie will-// be used in the token generation and verification algorithm and is expected to-// be present in all subsequent incoming requests.-//-// For every authorized request, the interceptor will also generate a-// cryptographically-safe XSRF token using the appKey, the cookie and the path-// visited. This can be later extracted using Token and should be injected as a-// hidden input field in HTML forms.-//-// In case of state changing requests (all except GET, HEAD and OPTIONS), the-// interceptor checks for the presence of the XSRF token in the request body-// (expected to have been injected) and validates it. func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {-	needsValidation := !statePreservingMethods[r.Method()]-	cookieID, err := r.Cookie(cookieIDKey)+	if statePreservingMethods[r.Method()] {+		return safehttp.NotWritten()+	}+	tok, userID, actionID, code := it.c.Retrieve(r)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	code = it.c.Validate(tok, userID, actionID)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	return safehttp.NotWritten()+}++func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	err := it.i.Inject(resp, w, r) 	if err != nil {-		if needsValidation {-			return w.WriteError(safehttp.StatusForbidden)-		}-		cookieID, err = addCookieID(w)-		if err != nil {-			// An error is returned when the plugin fails to Set the Set-Cookie-			// header in the response writer as this is a server misconfiguration.-			return w.WriteError(safehttp.StatusInternalServerError)-		}+		return w.WriteError(safehttp.StatusInternalServerError)+	}+	return safehttp.Result{}+}++// Checker decides whether a safehttp.IncomingRequest should be allowed to pass+// or it's part of a Cross-Site Request Forgery (XSRF) attack.+type Checker interface {+	// Retrieve returns the information necessary to verify the validity of+	// a XSRF token.+	Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode)+	// Validate validates the token contained in the request, optionally using+	// the user session and action the user is taking.+	Validate(token string, userID string, actionID string) safehttp.StatusCode+}++type Injector interface {+	// Inject adds the protection necessary to the safehttp.Response and+	// safehttp.ResponseWriter so that subsequent request can be deemed safe.+	Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error+}++type defaultChecker struct {+	secretAppKey string+	cookieIDKey  string+	tokenKey     string+}++func (c defaultChecker) Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode) {+	cookie, err := r.Cookie(c.cookieIDKey)+	if err != nil {+		code = safehttp.StatusForbidden+		return 	}+	userID = cookie.Value() -	actionID := r.URL.Path()-	if needsValidation {-		f, err := r.PostForm()+	f, err := r.PostForm()+	if err != nil {+		// We fallback to checking whether the form is multipart. Both types+		// are valid in an incoming request as long as the XSRF token is+		// present.+		mf, err := r.MultipartForm(32 << 20) 		if err != nil {-			// We fallback to checking whether the form is multipart. Both types-			// are valid in an incoming request as long as the XSRF token is-			// present.-			mf, err := r.MultipartForm(32 << 20)-			if err != nil {-				return w.WriteError(safehttp.StatusBadRequest)-			}-			f = &mf.Form+			code = safehttp.StatusBadRequest+			return 		}+		f = &mf.Form+	}++	token = f.String(c.tokenKey, "")+	if f.Err() != nil || token == "" {+		code = safehttp.StatusUnauthorized+		return+	} -		tok := f.String(TokenKey, "")-		if f.Err() != nil || tok == "" {-			return w.WriteError(safehttp.StatusUnauthorized)+	actionID = r.URL.Path()+	return+}++func (c defaultChecker) Validate(token, userID, actionID string) safehttp.StatusCode {+	if !xsrftoken.Valid(token, c.secretAppKey, userID, actionID) {+		return safehttp.StatusForbidden+	}+	return safehttp.StatusOK+}++type defaultInjector struct {+	cookieIDKey  string+	secretAppKey string+}++func (i defaultInjector) Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error {+	c, err := r.Cookie(i.cookieIDKey)+	if err != nil {+		buf := make([]byte, 20)+		if _, err := rand.Read(buf); err != nil {+			return fmt.Errorf("crypto/rand.Read: %v", err) 		}+		c = safehttp.NewCookie(i.cookieIDKey, base64.StdEncoding.EncodeToString(buf))+		c.SetSameSite(safehttp.SameSiteStrictMode) -		if ok := xsrftoken.Valid(tok, it.SecretAppKey, cookieID.Value(), actionID); !ok {-			return w.WriteError(safehttp.StatusForbidden)+		if err := w.SetCookie(c); err != nil {+			return err 		} 	} -	tok := xsrftoken.Generate(it.SecretAppKey, cookieID.Value(), actionID)-	r.SetContext(context.WithValue(r.Context(), tokenCtxKey{}, tok))-	return safehttp.NotWritten()-}+	tok := xsrftoken.Generate(i.secretAppKey, c.Value(), r.URL.Path()) -// Commit adds the XSRF token corresponding to the safehttp.TemplateResponse-// with key "XSRFToken". The token corresponds to the user information found in-// the request.-func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result { 	tmplResp, ok := resp.(safehttp.TemplateResponse) 	if !ok {-		return safehttp.NotWritten()-	}--	tok, err := Token(r)-	if err != nil {-		// The token should have been added in the Before stage and if that is-		// not the case, a server misconfiguration occured.-		return w.WriteError(safehttp.StatusInternalServerError)+		return nil 	}  	// TODO(maramihali@): Change the key when function names are exported by 	// htmlinject 	// TODO: what should happen if the XSRFToken key is not present in the 	// tr.FuncMap? 	tmplResp.FuncMap["XSRFToken"] = func() string { return tok }-	return safehttp.NotWritten()+	return nil+}++type angularChecker struct {+	tokenCookieName string+	tokenHeaderName string+}++func (c angularChecker) Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode) {+	cookie, err := r.Cookie(c.tokenCookieName)+	if err != nil {+		code = safehttp.StatusForbidden+		return

Try to avoid using naked returns, especially if you have patterns like

assignment to return value
naked return

This line would be more readable like return "","","", safehttp.StatusForbidden since it would highlight what you are doing.

I understand it is harder to write code like this, but I think the readability improvement is worth.

Same for other naked returns.

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	c            Checker+	i            Injector }  var _ safehttp.Interceptor = &Interceptor{} -type tokenCtxKey struct{}--// Token extracts the XSRF token from the incoming request. If it is not-// present, it returns a non-nil error.-func Token(r *safehttp.IncomingRequest) (string, error) {-	tok := r.Context().Value(tokenCtxKey{})-	if tok == nil {-		return "", errors.New("xsrf token not found")+func New(key string, c Checker, i Injector) Interceptor {

Nit: I would name the parameter secretAppKey.

maramihali

comment created time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string

Nit: maybe key will suffice?

maramihali

comment created time in 10 days

PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 package xsrf  import (-	"context" 	"crypto/rand" 	"encoding/base64"-	"errors" 	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" ) -const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token"-	cookieIDKey = "xsrf-cookie"-)- var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	c            Checker+	i            Injector }  var _ safehttp.Interceptor = &Interceptor{} -type tokenCtxKey struct{}--// Token extracts the XSRF token from the incoming request. If it is not-// present, it returns a non-nil error.-func Token(r *safehttp.IncomingRequest) (string, error) {-	tok := r.Context().Value(tokenCtxKey{})-	if tok == nil {-		return "", errors.New("xsrf token not found")+func New(key string, c Checker, i Injector) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c:            c,+		i:            i, 	}-	return tok.(string), nil } -func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {-	buf := make([]byte, 20)-	if _, err := rand.Read(buf); err != nil {-		return nil, fmt.Errorf("crypto/rand.Read: %v", err)+func Default(key string) Interceptor {+	return Interceptor{+		secretAppKey: key,+		c: defaultChecker{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+			tokenKey:     "xsrf-token",+		},+		i: defaultInjector{+			secretAppKey: key,+			cookieIDKey:  "xsrf-cookie",+		}, 	}+} -	c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))-	c.SetSameSite(safehttp.SameSiteStrictMode)-	if err := w.SetCookie(c); err != nil {-		return nil, err+func Angular(cookieName, headerName string) Interceptor {+	return Interceptor{+		c: angularChecker{+			tokenCookieName: cookieName,+			tokenHeaderName: headerName,+		},+		i: angularInjector{+			tokenCookieName: cookieName,+		}, 	}-	return c, nil } -// Before should be executed before directing the safehttp.IncomingRequest to-// the handler to ensure it is not part of a Cross-Site Request-// Forgery attack.-//-// On first user visit through a state preserving request (GET, HEAD or-// OPTIONS), a nonce-based cookie will be set in the response as a way to-// distinguish between users and prevent pre-login XSRF attacks. The cookie will-// be used in the token generation and verification algorithm and is expected to-// be present in all subsequent incoming requests.-//-// For every authorized request, the interceptor will also generate a-// cryptographically-safe XSRF token using the appKey, the cookie and the path-// visited. This can be later extracted using Token and should be injected as a-// hidden input field in HTML forms.-//-// In case of state changing requests (all except GET, HEAD and OPTIONS), the-// interceptor checks for the presence of the XSRF token in the request body-// (expected to have been injected) and validates it. func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {-	needsValidation := !statePreservingMethods[r.Method()]-	cookieID, err := r.Cookie(cookieIDKey)+	if statePreservingMethods[r.Method()] {+		return safehttp.NotWritten()+	}+	tok, userID, actionID, code := it.c.Retrieve(r)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	code = it.c.Validate(tok, userID, actionID)+	if code != safehttp.StatusOK {+		return w.WriteError(code)+	}+	return safehttp.NotWritten()+}++func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+	err := it.i.Inject(resp, w, r) 	if err != nil {-		if needsValidation {-			return w.WriteError(safehttp.StatusForbidden)-		}-		cookieID, err = addCookieID(w)-		if err != nil {-			// An error is returned when the plugin fails to Set the Set-Cookie-			// header in the response writer as this is a server misconfiguration.-			return w.WriteError(safehttp.StatusInternalServerError)-		}+		return w.WriteError(safehttp.StatusInternalServerError)+	}+	return safehttp.Result{}+}++// Checker decides whether a safehttp.IncomingRequest should be allowed to pass+// or it's part of a Cross-Site Request Forgery (XSRF) attack.+type Checker interface {+	// Retrieve returns the information necessary to verify the validity of+	// a XSRF token.+	Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode)+	// Validate validates the token contained in the request, optionally using+	// the user session and action the user is taking.+	Validate(token string, userID string, actionID string) safehttp.StatusCode+}++type Injector interface {+	// Inject adds the protection necessary to the safehttp.Response and+	// safehttp.ResponseWriter so that subsequent request can be deemed safe.+	Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error+}++type defaultChecker struct {+	secretAppKey string+	cookieIDKey  string+	tokenKey     string+}++func (c defaultChecker) Retrieve(r *safehttp.IncomingRequest) (token, userID, actionID string, code safehttp.StatusCode) {+	cookie, err := r.Cookie(c.cookieIDKey)+	if err != nil {+		code = safehttp.StatusForbidden+		return 	}+	userID = cookie.Value() -	actionID := r.URL.Path()-	if needsValidation {-		f, err := r.PostForm()+	f, err := r.PostForm()+	if err != nil {+		// We fallback to checking whether the form is multipart. Both types+		// are valid in an incoming request as long as the XSRF token is+		// present.+		mf, err := r.MultipartForm(32 << 20) 		if err != nil {-			// We fallback to checking whether the form is multipart. Both types-			// are valid in an incoming request as long as the XSRF token is-			// present.-			mf, err := r.MultipartForm(32 << 20)-			if err != nil {-				return w.WriteError(safehttp.StatusBadRequest)-			}-			f = &mf.Form+			code = safehttp.StatusBadRequest+			return 		}+		f = &mf.Form+	}++	token = f.String(c.tokenKey, "")+	if f.Err() != nil || token == "" {+		code = safehttp.StatusUnauthorized+		return+	} -		tok := f.String(TokenKey, "")-		if f.Err() != nil || tok == "" {-			return w.WriteError(safehttp.StatusUnauthorized)+	actionID = r.URL.Path()+	return+}++func (c defaultChecker) Validate(token, userID, actionID string) safehttp.StatusCode {+	if !xsrftoken.Valid(token, c.secretAppKey, userID, actionID) {+		return safehttp.StatusForbidden+	}+	return safehttp.StatusOK+}++type defaultInjector struct {+	cookieIDKey  string+	secretAppKey string+}++func (i defaultInjector) Inject(resp safehttp.Response, w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) error {+	c, err := r.Cookie(i.cookieIDKey)+	if err != nil {+		buf := make([]byte, 20)+		if _, err := rand.Read(buf); err != nil {+			return fmt.Errorf("crypto/rand.Read: %v", err) 		}+		c = safehttp.NewCookie(i.cookieIDKey, base64.StdEncoding.EncodeToString(buf))+		c.SetSameSite(safehttp.SameSiteStrictMode) -		if ok := xsrftoken.Valid(tok, it.SecretAppKey, cookieID.Value(), actionID); !ok {-			return w.WriteError(safehttp.StatusForbidden)+		if err := w.SetCookie(c); err != nil {+			return err 		} 	} -	tok := xsrftoken.Generate(it.SecretAppKey, cookieID.Value(), actionID)-	r.SetContext(context.WithValue(r.Context(), tokenCtxKey{}, tok))-	return safehttp.NotWritten()-}+	tok := xsrftoken.Generate(i.secretAppKey, c.Value(), r.URL.Path()) -// Commit adds the XSRF token corresponding to the safehttp.TemplateResponse-// with key "XSRFToken". The token corresponds to the user information found in-// the request.-func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result { 	tmplResp, ok := resp.(safehttp.TemplateResponse) 	if !ok {-		return safehttp.NotWritten()-	}--	tok, err := Token(r)-	if err != nil {-		// The token should have been added in the Before stage and if that is-		// not the case, a server misconfiguration occured.-		return w.WriteError(safehttp.StatusInternalServerError)+		return nil 	}  	// TODO(maramihali@): Change the key when function names are exported by 	// htmlinject 	// TODO: what should happen if the XSRFToken key is not present in the 	// tr.FuncMap? 	tmplResp.FuncMap["XSRFToken"] = func() string { return tok }

This constant is now exported by htmlinject, you might want to use that.

maramihali

comment created time in 10 days

PullRequestReviewEvent

create barnchgoogle/go-safeweb

branch : coop-refactor

created branch time in 10 days

Pull request review commentgoogle/go-safeweb

Refactored the XSRF plugin to a more general structure

 import ( 	"crypto/rand" 	"encoding/base64" 	"errors"-	"fmt"- 	"github.com/google/go-safeweb/safehttp" 	"golang.org/x/net/xsrftoken" )  const (-	// TokenKey is the form key used when sending the token as part of POST-	// request.-	TokenKey    = "xsrf-token" 	cookieIDKey = "xsrf-cookie"+	TokenKey    = "xsrf-token" ) +type tokenCtxKey struct{}+ var statePreservingMethods = map[string]bool{ 	safehttp.MethodGet:     true, 	safehttp.MethodHead:    true, 	safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {-	// SecretAppKey uniquely identifies each registered service and should have-	// high entropy as it is used for generating the XSRF token.-	SecretAppKey string+	secretAppKey string+	g            Generator+	c            Checker+	i            Injector

Checker should check whether the request follows XSRF protection protocol (i.e. for state-changing it should require an XSRF token; the Checker is responsible for taking the request and determining whether it is fine or not). If not, I'd make the Checker return that information as opposed to directly writing the response. The XSRF plugin itself should do it.

I agree

Generator should generate the XSRF tokens (or whatever needs to be in a cookie). It's not clear to me what it should return, but I don't think it should do anything with the ResponseWriter.

We cannot generalize this: some defences will be a cookie, some others will be a token for the template, some will be headers. How would you do it? And what would you inject, where and how?

maramihali

comment created time in 11 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventgoogle/go-safeweb

empijei

commit sha 514128f3f101da67a388966e579cf449a37304b5

htmlinject: provide helpers to load templates in a safe and ergonomic way

view details

empijei

commit sha 140071403cb62c8ad873813b54fa29c677ab5ef3

htmlinject: provide clearer configs

view details

empijei

commit sha 150831a41f1c6440f5ffa3a6945169ae5964af0a

htmlinject: address CR comments.

view details

push time in 11 days

PR merged google/go-safeweb

htmlinject: provide helpers to load safehtml.Templates

It was previously impossible to use htmlinject together with safehtml without an uncheckedconversion.

Depends on PR #197 Fixes #173

+278 -39

0 comment

6 changed files

empijei

pr closed time in 11 days

issue closedgoogle/go-safeweb

htmlinject: Provide a helper to parse templates

In order to use htmlinject the user needs to:

  • Create a new empty template
  • Call Funcs on it to make it so the functions Parse expects are defined (e.g. passing no-op functions that have the right signature and name for csrftoken and xsrftoken generation)
  • Load the template source
  • Use htmlinject to rewrite the template
  • Pass the result to Parse on the aforementioned template

The code would more or less look like this:

func parsetpl(path string) (*template.Template, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	noop := func() string { panic("this function should never be called") }
	t := template.New("").Funcs(map[string]interface{}{"CSPNonce": noop, "XSRFToken": noop})
	transf, err := htmlinject.Transform(f, htmlinject.CSPNoncesDefault, htmlinject.XSRFTokensDefault)
	if err != nil {
		return nil, err
	}
	return t.Parse(transf)
}

It would be nice if htmlinject provided helpers to perform all of those operations with a single call, and maybe even an helper to do so while loading templates from disk (like the standard html/template package does).

closed time in 11 days

empijei

push eventgoogle/go-safeweb

empijei

commit sha b669815af30586bf0f961a8a3615d263857b3c4f

htmlinject: provide helpers to load templates in a safe and ergonomic way

view details

empijei

commit sha c4e059412e627b378d127f62ee106b29c4fe96b5

htmlinject: provide clearer configs

view details

empijei

commit sha 76fa3b16583e3a77c0afef87fa56774e89c486c7

htmlinject: address CR comments.

view details

push time in 11 days

push eventgoogle/go-safeweb

empijei

commit sha 4bc871df1245b46c79a2beae79a21bcd1a4db7c1

Improved naming for SafeMuxBuilder to make it more clear.

view details

empijei

commit sha 907858f188b81a562db8c32c465f5b21aa6612fd

htmlinject: added rule for styles and exported useful constants.

view details

empijei

commit sha 763283a19bd42e2e953f42f5a9f79dffcfd0b043

Added COOP implementation.

view details

empijei

commit sha 99cac8da76106931d50a6df050f05f0e55bf7c9c

Added copyright to COOP plugin.

view details

empijei

commit sha 13783527bae3ac0a2250a4c69ca67888e732dc8d

coop: rename some unclear variables, add some docs.

view details

empijei

commit sha 22c967e82243230d97c43e7954766ab44dd071b2

htmlinject: provide helpers to load templates in a safe and ergonomic way

view details

empijei

commit sha 655ea3475a2a323a50b504c1df72d9189cdfb754

htmlinject: provide clearer configs

view details

empijei

commit sha abc6d38c1ce977259310f9494275682e04588421

htmlinject: address CR comments.

view details

empijei

commit sha 7578c7dc643607a6fbc56bf9008475c2618d4f24

Merge branch 'master' into htmlijloader

view details

push time in 11 days

push eventgoogle/go-safeweb

empijei

commit sha 22c967e82243230d97c43e7954766ab44dd071b2

htmlinject: provide helpers to load templates in a safe and ergonomic way

view details

empijei

commit sha 655ea3475a2a323a50b504c1df72d9189cdfb754

htmlinject: provide clearer configs

view details

empijei

commit sha abc6d38c1ce977259310f9494275682e04588421

htmlinject: address CR comments.

view details

push time in 11 days

more