Repository navigation
Expand file tree
/
Copy pathsecurity.html
More file actions
230 lines (206 loc) · 11 KB
/
Copy pathsecurity.html
File metadata and controls
230 lines (206 loc) · 11 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Security model — OBLITERATUS</title>
<meta name="theme-color" content="#202830">
<link rel="canonical" href="https://devcop95.github.io/pullgoscript/security.html">
<meta property="og:type" content="website">
<meta property="og:site_name" content="OBLITERATUS">
<meta property="og:title" content="Security model — OBLITERATUS">
<meta property="og:description" content="The OBLITERATUS threat model, trust boundary, security changes and documented limits.">
<meta property="og:url" content="https://devcop95.github.io/pullgoscript/security.html">
<meta property="og:image" content="https://devcop95.github.io/pullgoscript/og-image.jpg">
<meta property="og:image:secure_url" content="https://devcop95.github.io/pullgoscript/og-image.jpg">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="OBLITERATUS Windows Credential Exposure Auditor, in slate and cyan">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Security model — OBLITERATUS">
<meta name="twitter:description" content="The OBLITERATUS threat model, trust boundary, security changes and documented limits.">
<meta name="twitter:image" content="https://devcop95.github.io/pullgoscript/og-image.jpg">
<meta name="description" content="The OBLITERATUS threat model: what v1 exposed, what v2 does instead, where the trust boundary sits, and the two limits that are documented rather than hidden.">
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<link rel="alternate icon" href="favicon.ico" sizes="48x48">
<link rel="stylesheet" href="site.css">
</head>
<body>
<header class="top">
<div class="wrap">
<a class="brand" href="index.html">OBLITER<span>ATUS</span></a>
<nav class="main">
<a href="index.html#download">Download</a>
<a href="capabilities.html">Capabilities</a>
<a href="audit.html">Audit checks</a>
<a href="verify.html">Verify</a>
<a href="security.html" aria-current="page">Security model</a>
<a href="https://github.com/DevCop95/pullgoscript">GitHub</a>
</nav>
</div>
</header>
<main class="wrap">
<h1>Security model</h1>
<p class="lede">
A tool that demonstrates credential theft has to survive the question "what if it is pointed
at me?". v1 did not; v2 was rebuilt around the answer, and the test suite pins each control
closed.
</p>
<h2>What v1 got wrong</h2>
<div class="tw">
<table>
<caption>Severity as assessed against the v1 code.</caption>
<thead><tr><th>Sev</th><th>v1 problem</th><th>v2 control</th></tr></thead>
<tbody>
<tr>
<td class="sev sev-crit">CRIT</td>
<td>Listened on <code>:8080</code> — the console was LAN-reachable</td>
<td>Binds <code>127.0.0.1</code> only; <code>ListenAndServe</code> refuses a non-loopback address</td>
</tr>
<tr>
<td class="sev sev-crit">CRIT</td>
<td>Token derived from the PID — visible to any process — and echoed to unauthenticated <code>GET /</code></td>
<td>256-bit <code>crypto/rand</code> token, regenerated per run, compared in constant time</td>
</tr>
<tr>
<td class="sev sev-crit">CRIT</td>
<td><code>CheckOrigin: return true</code> — any web page could open the WebSocket and drive the console</td>
<td>Origin must be loopback <strong>and</strong> the <code>?t=</code> token must match</td>
</tr>
<tr>
<td class="sev sev-crit">CRIT</td>
<td>No Host validation — DNS rebinding could read the token out of the page</td>
<td>Every route rejects a non-loopback <code>Host</code></td>
</tr>
<tr>
<td class="sev sev-high">HIGH</td>
<td>Mixed GET mutations, no method enforcement</td>
<td><code>ServeMux</code> method patterns: automatic <code>405</code> on the wrong verb</td>
</tr>
<tr>
<td class="sev sev-high">HIGH</td>
<td>Token in HTML readable cross-origin (no CSP, no headers)</td>
<td>Same-origin only, plus CSP, <code>nosniff</code>, <code>DENY</code> framing, <code>no-store</code></td>
</tr>
<tr>
<td class="sev sev-high">HIGH</td>
<td>DOM XSS: database-derived URLs rendered through <code>innerHTML</code></td>
<td>All server data escaped through <code>window.esc()</code> before it reaches the DOM</td>
</tr>
<tr>
<td class="sev sev-med">MED</td>
<td>Auto-UAC on every start</td>
<td>Elevation only when asked for: <code>--elevate</code>, or a module that cannot work unelevated</td>
</tr>
<tr>
<td class="sev sev-med">MED</td>
<td>Credentialed collectors reachable by default</td>
<td>Locked behind <code>--enable-*</code>, failing closed with an actionable <code>403</code></td>
</tr>
<tr>
<td class="sev sev-med">MED</td>
<td>Privileged modules failed opaquely — SAM errors looked like "no SAM material", LSASS failures hung the UI</td>
<td>The real cause is streamed to the dashboard (<code>sam_error</code> / <code>lsass_error</code>), UAC is requested up front, and a relaunch that yields no elevated token fails loudly instead of looping</td>
</tr>
</tbody>
</table>
</div>
<h2>Where the boundary sits</h2>
<p>
Anyone who can read the dashboard page — loopback, same origin — <strong>is</strong> the
operator. That is the design: the console answers to the token, and the token is a lock on the
door, not on the room.
</p>
<p>Against a hostile <em>web page</em>, the boundary is airtight, and each layer is independent:</p>
<div class="cardgrid">
<div class="card">
<h3>It can reach the port</h3>
<p>But a cross-origin <code>fetch()</code> cannot read the response — same-origin policy.</p>
</div>
<div class="card">
<h3>It cannot set the header</h3>
<p>The custom <code>X-Signal-Token</code> header forces a CORS preflight, which is never approved, so the request is never sent. This is what protects state-changing routes from CSRF.</p>
</div>
<div class="card">
<h3>It cannot guess the token</h3>
<p>256 bits from <code>crypto/rand</code>, regenerated per run, compared in constant time.</p>
</div>
<div class="card">
<h3>Rebinding gets nothing</h3>
<p>A rebinding page reaches the port with its own hostname in <code>Host</code>, which is refused before any handler runs.</p>
</div>
</div>
<h2>Two limits, documented rather than hidden</h2>
<div class="callout warn">
<strong>1. The token defends against remote pages, not against local processes.</strong>
<code>GET /</code> serves the dashboard with the token inside it, and that route is guarded only
by the loopback <code>Host</code> check. Any process that can reach loopback — including one
running as a <em>different local user</em>, since loopback is machine-scoped — can read the token
and then use <code>POST /v1/sys/cmd</code> to execute commands as the user running the console.
The practical consequence is a deployment rule: <strong>run the console on a single-user
workstation</strong>. On a shared or RDP host it is a cross-user escalation path.
</div>
<div class="callout warn">
<strong>2. <code>POST /v1/sys/opt</code> changes the machine with no flag.</strong>
It writes HKLM policy and stops + disables the <code>DiagTrack</code> service, and unlike every
other state-changing capability it requires only the token — there is no
<code>--enable-*</code> gate, because the action is privacy hardening rather than credential
access. It is listed on the <a href="capabilities.html">Capabilities</a> page so nobody
discovers it by accident.
</div>
<p class="note">
Both are written into the repository's
<a href="https://github.com/DevCop95/pullgoscript/blob/main/SECURITY.md#documented-limits">SECURITY.md
under "Documented limits"</a> rather than left to be discovered by whoever reads the code next.
The discussion, the trade-offs and the rejected options live in
<a href="https://github.com/DevCop95/pullgoscript/issues/6">issue #6</a>, which is closed.
</p>
<h2>Intentional capabilities</h2>
<p>
These are features, not bugs — they are the reason the tool exists:
</p>
<ul>
<li><code>POST /v1/sys/cmd</code> executes shell commands <strong>as the token holder</strong>.</li>
<li><code>--enable-collect</code> decrypts browser credential stores and the local SAM/SYSTEM
hives <strong>on the local host</strong>.</li>
<li><code>--enable-inject</code> performs W^X process injection into an
<strong>operator-selected local PID</strong>.</li>
<li><code>--enable-persist</code> creates and removes local persistence footholds.</li>
<li><code>--enable-lateral</code> executes commands on <strong>operator-supplied remote
hosts</strong> over WinRM or SCM.</li>
<li><code>--enable-evasion</code> neuters ETW/AMSI <strong>in this process only</strong>.</li>
</ul>
<p>
They are reachable only over loopback, only with the per-run token, and the credential,
persistence, evasion, lateral and injection modules additionally require their flag at startup.
<strong>Hardening regressions in that chain are in scope</strong> for vulnerability reports.
</p>
<h2>Out of scope</h2>
<p>
Physical access, kernel-level attackers, and the operator console itself once the token holder
is trusted. Reports about the intentional capabilities above are answered with a pointer to this
page unless they show the <em>gate</em> failing — a route reachable without its flag, a
non-loopback bind, a missing Host or Origin check, a token comparison that leaks, or a
state-changing route that a hostile page can reach.
</p>
<h2>Reporting a vulnerability</h2>
<p>
Report privately, never in a public issue with exploit details: GitHub →
<strong>Security</strong> → <strong>Report a vulnerability</strong> on
<a href="https://github.com/DevCop95/pullgoscript/security/advisories/new">DevCop95/pullgoscript</a>,
or open a minimal issue titled <code>[SECURITY]</code> asking for a private channel. Include the
affected version or commit, impact, reproduction steps and any suggested fix. Expect an
acknowledgement within 72 hours and a status update within 10 business days; fixes are credited
in the release notes unless you prefer otherwise. The full policy is in
<a href="https://github.com/DevCop95/pullgoscript/blob/main/SECURITY.md">SECURITY.md</a>.
</p>
</main>
<footer>
<div class="wrap">
<span>Assume breach. Audit the credentials before someone else does.</span>
<span><a href="https://github.com/DevCop95/pullgoscript/blob/main/LICENSE">MIT</a></span>
</div>
</footer>
</body>
</html>