Skip to content

Commit cddc501

Browse files
committed
Downgrade cef.js-bridge-registered and firmware.image-shipped Confirmed -> Inferred
Attribution debt from this session. Both rules emitted Confirmed on evidence that was pattern-based, not proof-based, which is exactly what the ladder exists to prevent. cef.js-bridge-registered: fires on the literal string RegisterJsObject or JavascriptObjectRepository in a PE. That proves the assembly references the type. It does not prove the call is reached at runtime. Confidence now Inferred. Promotion to Confirmed requires an IL call-graph trace (that IL check is not run here). firmware.image-shipped: fires when a file with a firmware extension has the right magic bytes. That proves the file exists in that shape. It does not prove the vendor updater LOADS it or that it is flashed without a signature check. Confidence now Inferred. Invoke-TcpkFirmwarePlantProbe (K25) promotes to Confirmed (dynamic) when the read is observed. firmware.image-writable STAYS Confirmed. Both facts it rests on (file exists, DACL grants non-admin write) are directly observed here. The description now draws the line between what is proven (writable primitive) and what is inference (unsigned flash outcome) explicitly. Two Pester assertions updated to pin the new Confidence labels. One new assertion in IoTCompanion.Tests pins firmware.image-shipped == Inferred for every future run.
1 parent 0898d4e commit cddc501

5 files changed

Lines changed: 90 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ Release history for TCPK. Newest first.
44

55
## Unreleased
66

7+
**Attribution debt paid: `cef.js-bridge-registered` and `firmware.image-shipped` downgraded from
8+
Confirmed to Inferred.** Both rules emitted `Confirmed` on evidence that was pattern-based, not
9+
proof-based:
10+
11+
- `cef.js-bridge-registered` fires when the literal string `RegisterJsObject` or
12+
`JavascriptObjectRepository` appears in a PE. That proves the assembly references the type;
13+
it does not prove the call is reached. Confidence is now Inferred. Promotion to Confirmed
14+
requires an IL call-graph trace (`Invoke-TcpkDecompile` or the AI-verify layer).
15+
- `firmware.image-shipped` fires when a file with a firmware extension has the right magic
16+
bytes. That proves the file exists in that shape; it does not prove the vendor updater loads
17+
it, or that it is flashed without a signature check. Confidence is now Inferred.
18+
`Invoke-TcpkFirmwarePlantProbe` (K25) promotes to Confirmed (dynamic) when the read is
19+
observed.
20+
- `firmware.image-writable` STAYS Confirmed because both facts it rests on (file exists,
21+
DACL grants non-admin write) are directly observed here. Description tightened so the
22+
boundary between what is proven (writable primitive) and what is inference (unsigned flash
23+
outcome) is on the page.
24+
25+
Attribution ladder discipline: `Confirmed` is reserved for facts the rule directly observes,
26+
not for downstream framing the description implies. Two Pester tests updated to pin the new
27+
labels.
28+
729
**Test-TcpkUserRules (A54) - user-authored check format, phase 1.**
830

931
Anyone can now add a detection without touching PowerShell. Drop a JSON rule into

TCPK/Public/Discovery/Test-TcpkCefSharp.ps1

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ function Test-TcpkCefSharp {
1212
1313
Detected here, in one pass over each PE:
1414
15-
cef.js-bridge-registered HIGH RegisterJsObject or JavascriptObjectRepository.Register
16-
is called. The renderer has a bridge into managed code.
15+
cef.js-bridge-registered HIGH Inferred The literal string RegisterJsObject or
16+
JavascriptObjectRepository appears in the assembly. That
17+
is textual evidence of the API reference; a proven call
18+
site requires an IL check that this rule does not run,
19+
so Confidence is Inferred not Confirmed.
1720
1821
cef.remote-debugging-enabled HIGH CefSettings.RemoteDebuggingPort is set. DevTools is
1922
reachable on localhost with no authentication, so any
@@ -32,10 +35,12 @@ function Test-TcpkCefSharp {
3235
cef.uses-cefsharp INFO Assembly references CefSharp but none of the flags
3336
above matched. Scoping only.
3437
35-
This is INFERENCE. String presence in an assembly proves the type is referenced, not that
36-
it is reached at runtime. Confidence = Inferred on the config-flag rules and Confirmed on
37-
the bridge-registered rule, since a call to RegisterJsObject in shipped IL is the observed
38-
behaviour rather than a possibility.
38+
This is INFERENCE. String presence in an assembly proves the assembly REFERENCES the type,
39+
not that the call is reached at runtime. Every rule here emits Confidence = Inferred; an
40+
IL check that traces from the type reference to an actual call site would justify
41+
Confirmed, and it is not run here. The static Confirmed grade is reserved for facts the
42+
file alone proves (a shipped firmware image existing on disk, a user-writable DACL); a
43+
call-graph reachability claim is not one of those.
3944
4045
.PARAMETER Path
4146
Install directory or a single .NET assembly.
@@ -87,20 +92,22 @@ function Test-TcpkCefSharp {
8792
if (-not $seen.Add($key)) { continue }
8893
$fileHadHigher = $true
8994
New-TcpkFinding -Module 'discovery' -RuleId 'cef.js-bridge-registered' `
90-
-Severity 'HIGH' -Confidence 'Confirmed' `
91-
-Title "$($pe.Name) registers a JavaScript-to-native bridge ($api)" `
92-
-File $pe.FullName -Evidence "api=$api" `
95+
-Severity 'HIGH' -Confidence 'Inferred' `
96+
-Title "$($pe.Name) references the CefSharp JS-to-native bridge API ($api)" `
97+
-File $pe.FullName -Evidence "api=$api (string reference in the PE; call site not traced)" `
9398
-Cwe @('CWE-749') `
94-
-Description ('The assembly calls into the CefSharp bridge that exposes .NET objects ' +
95-
'to the embedded browser. Every method on the registered object becomes callable ' +
96-
'from any JavaScript in any frame the renderer processes. If the browser loads any ' +
97-
'HTML that is not fully first-party (a help page, a payment redirect, a documentation ' +
98-
'iframe, a login redirect back from an IdP, or an XSS in vendor content) that code ' +
99-
'now runs against process-privileged .NET.') `
100-
-Fix ('Prefer JavascriptObjectRepository with async bindings and NameConverter set to a ' +
101-
'strict subset; register a purpose-built bridge object with only the methods you want ' +
102-
'to expose, not a domain type. Consider CEF ExtensionSettings and process-per-site to ' +
103-
'contain a compromised renderer.')
99+
-Description ('The assembly REFERENCES the CefSharp bridge that exposes .NET objects to ' +
100+
'the embedded browser. The literal API name (' + $api + ') is present in the PE, so ' +
101+
'the type is imported. This rule does NOT prove that the call is reached at runtime; ' +
102+
'that would require an IL call-graph trace. Confidence is Inferred until that IL check ' +
103+
'runs. If the call IS reached, every method on the registered bridge object becomes ' +
104+
'callable from any JavaScript the renderer processes: a documentation iframe, a login ' +
105+
'redirect back from an IdP, an XSS in vendor content, all reach process-privileged .NET.') `
106+
-Fix ('Confirm the call site with an IL decompiler (Invoke-TcpkDecompile) or the AI-verify ' +
107+
'layer, then: prefer JavascriptObjectRepository with async bindings and NameConverter ' +
108+
'set to a strict subset; register a purpose-built bridge with only the methods you want ' +
109+
'to expose, not a domain type; consider CEF ExtensionSettings and process-per-site so ' +
110+
'a compromised renderer is contained.')
104111
break
105112
}
106113
}

TCPK/Public/Discovery/Test-TcpkFirmwareImages.ps1

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,43 @@ function Test-TcpkFirmwareImages {
9999
}
100100
} catch { }
101101

102-
$sev = if ($writable) { 'HIGH' } else { 'MEDIUM' }
102+
$sev = if ($writable) { 'HIGH' } else { 'MEDIUM' }
103103
$rule = if ($writable) { 'firmware.image-writable' } else { 'firmware.image-shipped' }
104+
# Attribution ladder: 'firmware.image-writable' is Confirmed because both facts it
105+
# rests on (the file exists at this path, the DACL grants non-admin write) are
106+
# directly observed by this rule. 'firmware.image-shipped' has only the file-exists
107+
# fact directly observed; the claim that the vendor's updater LOADS it and flashes it
108+
# without a signature check is inference. K25 Invoke-TcpkFirmwarePlantProbe promotes
109+
# the shipped case to Confirmed (dynamic) when the read is observed.
110+
$conf = if ($writable) { 'Confirmed' } else { 'Inferred' }
104111
$evParts = @("kind=$kind", "ext=$ext", "size=$len")
105112
if ($mismatch) { $evParts += 'header=missing' }
106113
if ($writable) { $evParts += 'writable-by-users=true' }
107114

115+
$desc = if ($writable) {
116+
('A firmware image is present in the install tree AND the resting DACL grants write ' +
117+
'to a non-admin group. Both facts are directly observed here. The exploit outcome ' +
118+
'(a modified image is flashed to the physical device on the next update) requires ' +
119+
'the vendor updater to READ this file and to flash it without a signature check. ' +
120+
'That second half is not proven by this rule; run Invoke-TcpkFirmwarePlantProbe ' +
121+
'(K25) to confirm dynamically. Local write on a firmware image that the updater ' +
122+
'does read and flash unsigned is the primitive Evil PLC and TRITON traced from an ' +
123+
'engineering workstation into controller code.')
124+
} else {
125+
('A firmware image is present in the install tree. Only this fact is directly ' +
126+
'observed here (file exists, header magic matches). Whether the vendor updater ' +
127+
'actually reads it at flash time, and whether it verifies a vendor signature over ' +
128+
'the payload, is INFERENCE. Confidence is Inferred until Invoke-TcpkFirmwarePlantProbe ' +
129+
'(K25) runs and reports a read by the updater PID, or Test-TcpkUpdateFlow (F02) ' +
130+
'reports missing signature primitives near the update code path.')
131+
}
132+
108133
New-TcpkFinding -Module 'discovery' -RuleId $rule `
109-
-Severity $sev -Confidence 'Confirmed' `
134+
-Severity $sev -Confidence $conf `
110135
-Title "$($f.Name) is a device firmware image ($kind)" `
111136
-File $f.FullName -Evidence ($evParts -join ' ') `
112137
-Cwe @('CWE-494') `
113-
-Description ('A firmware image sits inside the install tree. If the desktop tool that flashes ' +
114-
'or updates a device reads its payload from this path without verifying a vendor signature, ' +
115-
'local write on this file becomes remote code on the device. This is the exact seam Evil PLC ' +
116-
'and TRITON traced from an engineering workstation into controller code.' +
117-
$(if ($writable) { ' The resting DACL grants write to a non-admin group, so no elevation is needed to plant a modified image.' } else { '' })) `
138+
-Description $desc `
118139
-Fix ('Verify a vendor signature on every image before it is transmitted to the device, and ' +
119140
'restrict the image directory ACL to the installer identity (SYSTEM or the vendor service account). ' +
120141
'A code-signed image the desktop tool refuses to flash on signature failure closes the whole class.')

TCPK/Tests/CefSharp.Tests.ps1

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ AfterAll {
3636
}
3737

3838
Describe 'Test-TcpkCefSharp' {
39-
It 'reports a bridge registration as HIGH Confirmed' {
39+
It 'reports a bridge registration as HIGH Inferred (string presence, not proven call site)' {
40+
# Attribution ladder: this rule sees the API name in the PE. It does not run an IL
41+
# trace to prove the call is reached, so Confidence is Inferred. Promoting to Confirmed
42+
# requires an IL call-graph check (Invoke-TcpkDecompile / AI-verify).
4043
InModuleScope TCPK -Parameters @{ p = $script:bridge } {
4144
param($p)
4245
$rows = @(Test-TcpkCefSharp -Path $p)
4346
$r = $rows | Where-Object { $_.RuleId -eq 'cef.js-bridge-registered' }
4447
$r | Should -Not -BeNullOrEmpty
4548
$r.Severity | Should -Be 'HIGH'
46-
$r.Confidence | Should -Be 'Confirmed'
49+
$r.Confidence | Should -Be 'Inferred'
4750
}
4851
}
4952

TCPK/Tests/IoTCompanion.Tests.ps1

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ Describe 'Test-TcpkFirmwareImages' {
5151
$f[0].Evidence | Should -Match 'kind=UF2'
5252
}
5353

54+
It 'grades firmware.image-shipped as Inferred (file existence Confirmed, attack primitive is inference)' {
55+
# Attribution ladder: this rule proves the file exists. It does not prove the vendor
56+
# updater reads it or flashes it unsigned. Confidence is Inferred; Invoke-TcpkFirmwarePlantProbe (K25)
57+
# promotes to Confirmed (dynamic) when the read is observed.
58+
$f = @(Test-TcpkFirmwareImages -Path $script:work | Where-Object { $_.RuleId -eq 'firmware.image-shipped' })
59+
$f.Count | Should -BeGreaterThan 0
60+
foreach ($row in $f) { $row.Confidence | Should -Be 'Inferred' }
61+
}
62+
5463
It 'reports the ELF and the Intel HEX text file' {
5564
$rows = @(Test-TcpkFirmwareImages -Path $script:work)
5665
($rows | Where-Object { $_.File -like '*firmware.elf' }).Evidence | Should -Match 'kind=ELF'

0 commit comments

Comments
 (0)