-
Notifications
You must be signed in to change notification settings - Fork 15
/
FiddlerExtension.cs
156 lines (136 loc) · 6.42 KB
/
FiddlerExtension.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Fiddler;
[assembly: Fiddler.RequiredVersion("2.4.9.3")]
namespace FiddlerCSP
{
public class FiddlerExtension : IAutoTamper3, IDisposable
{
public static class Settings
{
private const string prefix = "FiddlerCSPExtension.";
public static bool verboseLogging
{
get { return Fiddler.FiddlerApplication.Prefs.GetBoolPref(prefix + "verboseLogging", false); }
set { Fiddler.FiddlerApplication.Prefs.SetBoolPref(prefix + "verboseLogging", value); }
}
public static bool enabled
{
get { return Fiddler.FiddlerApplication.Prefs.GetBoolPref(prefix + "enabled", false); }
set { Fiddler.FiddlerApplication.Prefs.SetBoolPref(prefix + "enabled", value); }
}
}
private class FiddlerAppLogger : ILogger
{
public void Log(string message)
{
if (Settings.verboseLogging)
{
FiddlerApplication.Log.LogString("FiddlerCSP: " + message);
}
}
}
public static string reportHost = "fiddlercsp.deletethis.net";
private ILogger logger;
private CSPRuleCollector collector;
public FiddlerExtension()
{
logger = new FiddlerAppLogger();
collector = new CSPRuleCollector(logger);
}
public void OnPeekAtRequestHeaders(Session oSession) { }
public void OnPeekAtResponseHeaders(Session oSession) { }
public void AutoTamperRequestAfter(Session oSession) { }
public void AutoTamperRequestBefore(Session session)
{
if (Settings.enabled && session.HostnameIs(reportHost) && !session.isFTP)
{
// TODO: We should offer an option to hide the reports from Fiddler; change "ui-strikeout" to "ui-hide" in the next line
session["ui-strikeout"] = "CSPReportGenerator";
if (!session.HTTPMethodIs("CONNECT"))
{
session.utilCreateResponseAndBypassServer();
session.oResponse.headers.Add("Content-Type", "text/html");
session.ResponseBody = Encoding.UTF8.GetBytes("<!doctype html><HTML><BODY><H1>Report received.</H1></BODY></HTML>");
ProcessCSPReport(session);
}
else
{
session["x-replywithtunnel"] = "CSPReportGenerator";
}
}
}
private void ProcessCSPReport(Session session)
{
string requestBody = session.GetRequestBodyAsString();
if (requestBody.Length > 0)
{
try
{
CSPReport cspReport = CSPReport.Parse(requestBody);
if (cspReport.cspReport != null && cspReport.cspReport.documentUri != null)
{
logger.Log("Got report for " + cspReport.cspReport.documentUri + " via " + session.fullUrl);
}
logger.Log("Adding " + cspReport.ToString());
collector.Add(cspReport, session.PathAndQuery == "/unsafe-eval" ?
CSPRuleCollector.InterpretBlank.UnsafeEval : CSPRuleCollector.InterpretBlank.UnsafeInline);
logger.Log("Total " + collector.ToString());
}
catch (Exception exception)
{
logger.Log("Invalid CSP - " + exception);
}
}
}
public void AutoTamperResponseAfter(Session oSession) { }
public void AutoTamperResponseBefore(Session session)
{
if (Settings.enabled && !session.isTunnel && !session.isFTP)
{
// Use https report URI for https sites because otherwise Chrome won't report.
// Use http report URI for http sites because Fiddler might not be configured to MitM https.
string reportUri = (session.isHTTPS ? "https" : "http") + "://" + reportHost;
// child-src generates a complaint in FireFox as apparently it isn't implemented.
string CSPROCommon = "child-src 'none'; connect-src 'none'; font-src 'none'; frame-src 'none'; img-src 'none'; media-src 'none'; object-src 'none'; style-src 'none'; ";
// Two different CSP-Report-Only headers with different report URIs so that we can tell the difference between unsafe-eval and unsafe-inline since they're
// both reported as empty string blocked-uri properties. Sort of a CSP spec problem.
session.oResponse.headers.Add("Content-Security-Policy-Report-Only", CSPROCommon + "script-src 'unsafe-eval'; report-uri " + reportUri + "/unsafe-inline");
session.oResponse.headers.Add("Content-Security-Policy-Report-Only", "script-src 'unsafe-inline'; report-uri " + reportUri + "/unsafe-eval");
session.oResponse.headers.Add("X-Fiddled-With-By", "FiddlerCSP");
// Set cache headers to not cache response since we're modifying the headers and don't want browsers to remember this.
session.oResponse.headers.Remove("Cache-Control");
session.oResponse.headers.Add("Cache-Control", "private, max-age=0, no-cache");
session.oResponse.headers.Remove("Expires");
session.oResponse.headers.Add("Expires", "Thu, 01 Dec 1983 20:00:00 GMT");
logger.Log("Adding report-only to " + session.fullUrl);
}
}
public void OnBeforeReturningError(Session oSession) { }
public void OnBeforeUnload() { }
public void OnLoad()
{
AddTab();
}
private void AddTab()
{
TabPage page = new TabPage("CSP Rule Collector");
var ruleCollectionView = new RuleCollectionView(collector);
ruleCollectionView.Dock = DockStyle.Fill;
page.Controls.Add(ruleCollectionView);
FiddlerApplication.UI.tabsViews.TabPages.Add(page);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool managedAndNative)
{
collector.Dispose();
}
}
}