Skip to content

Commit 05a8ecf

Browse files
r4ulclclaude
andcommitted
flag copy button: fix SyntaxError from a CRLF-terminated flag, keep key selectable
FLAG_MGT_RELAY_TABLETS decoded to "flag{...}\r\n". Both templates interpolated the decoded flag straight into onclick="copyFlagToClipboard('<flag>')", so the trailing CRLF landed inside a JS string literal and the parser threw "'' string literal contains an unescaped line break" -- the copy button was dead on 192.168.18.1 (IP_MGT_RELAY_TABLETS) only. Scanned every FLAG_*/PASS_* in wlan_config and wlan_config_challenge; that was the only corrupted value. - wlan_config: re-encode FLAG_MGT_RELAY_TABLETS without the trailing CRLF. - login.php.tmp / index.php.tmp: stop inlining the key into JS. trim() + htmlspecialchars() and emit <div class="flag"><code class="flag-text">key </code><button class="flag-copy">, so no flag content can break the page. - script.js: delegated .flag-copy listener reads the key from .flag-text textContent; keeps the execCommand fallback (lab is plain HTTP, so navigator.clipboard is undefined) and reports COPIED / SELECT + CTRL-C. - style.css: the key is now a <code> with user-select: text so it can be drag-selected by hand; COPY is a separate control beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 44d90b0 commit 05a8ecf

5 files changed

Lines changed: 90 additions & 53 deletions

File tree

‎APs/config/html/index.php.tmp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ function matchnet($ip, $list) {
3838
}
3939

4040
function flagReadout($decoded) {
41-
return "<div class=\"readout\"><div class=\"readout-top\"><span class=\"bars\" aria-hidden=\"true\"><i></i><i></i><i></i><i></i></span>Access key recovered</div><button class=\"flag\" onclick=\"copyFlagToClipboard('{$decoded}')\">{$decoded}</button></div>";
41+
$f = htmlspecialchars(trim($decoded), ENT_QUOTES, 'UTF-8');
42+
return "<div class=\"readout\"><div class=\"readout-top\"><span class=\"bars\" aria-hidden=\"true\"><i></i><i></i><i></i><i></i></span>Access key recovered</div>"
43+
. "<div class=\"flag\"><code class=\"flag-text\">{$f}</code><button type=\"button\" class=\"flag-copy\" aria-label=\"Copy access key\">COPY</button></div></div>";
4244
}
4345

4446
function renderStatus($user, $ip, $a, $rules) {

‎APs/config/html/login.php.tmp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ $flag_6ghz = decode('${FLAG_6GHZ}', $a);
111111
$flag_roam = decode('${FLAG_ROAM}', $a);
112112

113113
function loginflag($flag) {
114-
echo "<div class=\"readout\"><div class=\"readout-top\"><span class=\"bars\" aria-hidden=\"true\"><i></i><i></i><i></i><i></i></span>Access key recovered</div><button class=\"flag\" onclick=\"copyFlagToClipboard('{$flag}')\">{$flag}</button></div>";
114+
$f = htmlspecialchars(trim($flag), ENT_QUOTES, 'UTF-8');
115+
echo "<div class=\"readout\"><div class=\"readout-top\"><span class=\"bars\" aria-hidden=\"true\"><i></i><i></i><i></i><i></i></span>Access key recovered</div>"
116+
. "<div class=\"flag\"><code class=\"flag-text\">{$f}</code><button type=\"button\" class=\"flag-copy\" aria-label=\"Copy access key\">COPY</button></div></div>";
115117
}
116118

117119
if (strpos($_SERVER['REMOTE_ADDR'], '${IP_MGT_RELAY}.') === 0)

‎APs/config/html/script.js‎

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,59 @@
11
/* WiFiChallenge by r4ulcl · copy recovered key off the device */
2-
function copyFlagToClipboard(flag) {
3-
var e = window.event;
4-
var btn = e && e.target && e.target.closest ? e.target.closest('button') : null;
52

6-
function done() {
7-
if (!btn) return;
8-
btn.classList.add('copied');
9-
setTimeout(function () { btn.classList.remove('copied'); }, 1500);
3+
/* Fallback for insecure contexts (plain HTTP, where navigator.clipboard is
4+
undefined). Returns true only if the copy actually succeeded. */
5+
function copyLegacy(text) {
6+
try {
7+
var ta = document.createElement('textarea');
8+
ta.value = text;
9+
ta.setAttribute('readonly', '');
10+
ta.style.position = 'fixed';
11+
ta.style.top = '-1000px';
12+
ta.style.opacity = '0';
13+
document.body.appendChild(ta);
14+
ta.select();
15+
ta.setSelectionRange(0, ta.value.length);
16+
var ok = document.execCommand('copy');
17+
document.body.removeChild(ta);
18+
return ok;
19+
} catch (err) {
20+
return false;
1021
}
22+
}
1123

12-
/* Fallback for insecure contexts (plain HTTP, where navigator.clipboard
13-
is undefined). Returns true only if the copy actually succeeded. */
14-
function legacy() {
15-
try {
16-
var ta = document.createElement('textarea');
17-
ta.value = flag;
18-
ta.setAttribute('readonly', '');
19-
ta.style.position = 'fixed';
20-
ta.style.top = '-1000px';
21-
ta.style.opacity = '0';
22-
document.body.appendChild(ta);
23-
ta.select();
24-
ta.setSelectionRange(0, ta.value.length);
25-
var ok = document.execCommand('copy');
26-
document.body.removeChild(ta);
27-
return ok;
28-
} catch (err) {
29-
return false;
30-
}
24+
function copyFlagToClipboard(flag, btn) {
25+
function done(ok) {
26+
if (!btn) return;
27+
var label = btn.getAttribute('data-label') || btn.textContent;
28+
btn.setAttribute('data-label', label);
29+
btn.classList.remove('copied', 'failed');
30+
btn.classList.add(ok ? 'copied' : 'failed');
31+
btn.textContent = ok ? 'COPIED' : 'SELECT + CTRL-C';
32+
setTimeout(function () {
33+
btn.classList.remove('copied', 'failed');
34+
btn.textContent = label;
35+
}, 1500);
3136
}
3237

3338
if (navigator.clipboard && navigator.clipboard.writeText) {
34-
navigator.clipboard.writeText(flag).then(done, function () { if (legacy()) done(); });
35-
} else if (legacy()) {
36-
done();
39+
navigator.clipboard.writeText(flag).then(
40+
function () { done(true); },
41+
function () { done(copyLegacy(flag)); }
42+
);
43+
} else {
44+
done(copyLegacy(flag));
3745
}
3846
}
47+
48+
/* Delegated: the flag text lives in .flag > .flag-text so it stays selectable
49+
with the mouse, and the COPY button never carries the key inline. */
50+
document.addEventListener('click', function (ev) {
51+
var btn = ev.target && ev.target.closest ? ev.target.closest('.flag-copy') : null;
52+
if (!btn) return;
53+
54+
var wrap = btn.closest('.flag');
55+
var text = wrap ? wrap.querySelector('.flag-text') : null;
56+
if (!text) return;
57+
58+
copyFlagToClipboard(text.textContent.trim(), btn);
59+
});

‎APs/config/html/style.css‎

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -280,40 +280,52 @@ button:disabled {
280280
.bars i:nth-child(3) { height: 78%; animation-delay: 0.25s; }
281281
.bars i:nth-child(4) { height: 100%; animation-delay: 0.35s; }
282282

283-
/* the flag itself is emitted as a <button> by PHP */
284-
.readout button,
285-
button.flag {
286-
width: 100%;
287-
margin: 0;
283+
/* the key text is selectable; the COPY button sits next to it */
284+
.flag {
285+
display: flex;
286+
align-items: stretch;
287+
gap: 0.5rem;
288+
background: #191a21;
289+
border: 1px solid rgba(80, 250, 123, 0.35);
290+
border-radius: 6px;
291+
padding: 0.7rem 0.7rem 0.7rem 0.9rem;
292+
}
293+
.flag:hover { border-color: var(--green); }
294+
295+
.flag-text {
296+
flex: 1 1 auto;
297+
min-width: 0;
288298
letter-spacing: 0.02em;
289299
font-family: var(--mono);
290300
font-size: 13px;
291301
font-weight: 600;
292-
text-align: left;
293302
color: var(--green);
294-
background: #191a21;
295-
border: 1px solid rgba(80, 250, 123, 0.35);
296-
border-radius: 6px;
297-
padding: 0.7rem 4rem 0.7rem 0.9rem;
298-
position: relative;
299303
white-space: nowrap;
300304
overflow-x: auto;
305+
/* let the user drag-select the key by hand */
306+
-webkit-user-select: text;
307+
user-select: text;
308+
cursor: text;
301309
}
302-
.readout button:hover,
303-
button.flag:hover { background: #14251b; border-color: var(--green); }
304-
.readout button::after,
305-
button.flag::after {
306-
content: "COPY";
307-
position: absolute;
308-
top: 0.6rem;
309-
right: 0.7rem;
310+
311+
.flag-copy {
312+
flex: 0 0 auto;
313+
width: auto;
314+
margin: 0;
315+
align-self: center;
310316
font-family: var(--mono);
311317
font-size: 9px;
312318
letter-spacing: 0.14em;
313319
color: var(--ink-faint);
320+
background: transparent;
321+
border: 1px solid rgba(80, 250, 123, 0.25);
322+
border-radius: 4px;
323+
padding: 0.35rem 0.55rem;
324+
cursor: pointer;
314325
}
315-
.readout button.copied::after,
316-
button.flag.copied::after { content: "COPIED"; color: var(--green); }
326+
.flag-copy:hover { color: var(--green); border-color: var(--green); background: #14251b; }
327+
.flag-copy.copied { color: var(--green); border-color: var(--green); }
328+
.flag-copy.failed { color: #ff5555; border-color: #ff5555; }
317329

318330
/* --------------------------------------------------------------- states */
319331

‎wlan_config‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ IP_MGT_RELAY_TABLETS='192.168.18'
236236
MAC_MGT_RELAY_TABLETS='F0:9F:C2:7A:33:28'
237237
NZYME_FINGERPRINT_MGT_RELAY_TABLETS='2d5d13288a250894738458150d9c884351b18f11f0abfdab3717a42a8e763592'
238238
CHANNEL_MGT_RELAY_TABLETS='44'
239-
FLAG_MGT_RELAY_TABLETS='LF8FUh4QCncWThU8f2FfPjFgLUhyU2AafVUBBVBAVndHGBY6eGYJbmYxKBMkHF9y'
239+
FLAG_MGT_RELAY_TABLETS='LF8FUh4QCncWThU8f2FfPjFgLUhyU2AafVUBBVBAVndHGBY6eGYJbmYxKBMkHA=='
240240

241241
############ CLIENTS ############
242242
WLAN_CLIENT_MGT_RELAY_TABLETS_W='wlan54'

0 commit comments

Comments
 (0)