fix(av-surface): no runnable base64-to-shell line on disk
PLAN § v8.1.3 tillegg a. The bash-evasion gallery's T8 comment held a complete base64-decode-into-shell line whose 16-char blob decodes to a home-directory deletion, under probe (b)'s 24-char floor. Probe (b2) found two more of the same shape, fixed here too: the gallery README's T8 row and tests/e2e/attack-chain.test.mjs (an /etc deletion). Chosen over an exception list because the gate should have none. The gallery and the e2e test now encode the blob at runtime from the same fragments; the README shows a placeholder. Gallery: 10/10 blocked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
62e3cade60
commit
58d2e8ad8d
4 changed files with 12 additions and 8 deletions
|
|
@ -35,7 +35,7 @@ techniques below are caught.
|
||||||
| T5 | IFS word-splitting | `rm${IFS}-rf${IFS}$HOME` | `rm -rf $HOME` |
|
| T5 | IFS word-splitting | `rm${IFS}-rf${IFS}$HOME` | `rm -rf $HOME` |
|
||||||
| T6 | ANSI-C hex quoting | `$'\x72\x6d' -rf $HOME` | `rm -rf $HOME` |
|
| T6 | ANSI-C hex quoting | `$'\x72\x6d' -rf $HOME` | `rm -rf $HOME` |
|
||||||
| T7 | process substitution | `cat <(echo rm) -rf $HOME` | `cat echo rm -rf $HOME` |
|
| T7 | process substitution | `cat <(echo rm) -rf $HOME` | `cat echo rm -rf $HOME` |
|
||||||
| T8 | base64-pipe-shell idiom | `echo cm0gLXJmICRIT01F \| base64 -d \| sh` | (separate BLOCK_RULE — not normalization) |
|
| T8 | base64-pipe-shell idiom | `echo <base64 of the baseline command> \| base64 -d \| sh` | (separate BLOCK_RULE — not normalization) |
|
||||||
| T9 | eval-via-variable (one-level forward flow) | `X=rm; $X -rf $HOME` | `X=rm; rm -rf $HOME` |
|
| T9 | eval-via-variable (one-level forward flow) | `X=rm; $X -rf $HOME` | `X=rm; rm -rf $HOME` |
|
||||||
|
|
||||||
The canonical destructive target throughout the gallery is
|
The canonical destructive target throughout the gallery is
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ Code 2.1.98+ harness-level checks.
|
||||||
in `expected-findings.md` for documentation; the run script
|
in `expected-findings.md` for documentation; the run script
|
||||||
only checks exit code 2, so it continues to pass after a
|
only checks exit code 2, so it continues to pass after a
|
||||||
rename)
|
rename)
|
||||||
- The base64 blob in T8 (`cm0gLXJmICRIT01F`) decodes to the
|
- The base64 blob in T8 (`T8_BLOB`) is built at runtime from the
|
||||||
literal command. If you change the canonical destructive
|
same fragments as the baseline command, so changing the
|
||||||
target away from `$HOME`, regenerate the blob with
|
canonical destructive target updates it too. No runnable
|
||||||
`printf '<new-cmd>' | base64`
|
base64-to-shell line is kept on disk (v8.1.3)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ const RM = 'r' + 'm';
|
||||||
const RF = '-' + 'rf';
|
const RF = '-' + 'rf';
|
||||||
const TARGET = '$' + 'HOME';
|
const TARGET = '$' + 'HOME';
|
||||||
const PIPE_SH = '| ' + 'sh';
|
const PIPE_SH = '| ' + 'sh';
|
||||||
|
// T8's blob is the canonical command base64-encoded at runtime, so no
|
||||||
|
// runnable base64-to-shell line sits in this file (v8.1.3).
|
||||||
|
const T8_BLOB = Buffer.from(`${RM} ${RF} ${TARGET}`).toString('base64');
|
||||||
|
|
||||||
const CASES = [
|
const CASES = [
|
||||||
{
|
{
|
||||||
|
|
@ -108,8 +111,8 @@ const CASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: 'T8 — base64-pipe-shell idiom',
|
tag: 'T8 — base64-pipe-shell idiom',
|
||||||
// echo cm0gLXJmICRIT01F | base64 -d | sh — base64 of "rm -rf $HOME"
|
// echo <T8_BLOB>, decoded and piped into a shell
|
||||||
command: `echo cm0gLXJmICRIT01F | base64 -d ${PIPE_SH}`,
|
command: `echo ${T8_BLOB} | base64 -d ${PIPE_SH}`,
|
||||||
note: 'separate BLOCK_RULE — not a normalization, but the same shape',
|
note: 'separate BLOCK_RULE — not a normalization, but the same shape',
|
||||||
expectBlock: true,
|
expectBlock: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,8 @@ describe('e2e attack-chain — Stage 2: destructive Bash with evasion', () => {
|
||||||
it('blocks T8 base64-pipe-shell (echo … | base64 -d | bash)', async () => {
|
it('blocks T8 base64-pipe-shell (echo … | base64 -d | bash)', async () => {
|
||||||
const result = await runHook(BASH_GUARD, {
|
const result = await runHook(BASH_GUARD, {
|
||||||
tool_name: 'Bash',
|
tool_name: 'Bash',
|
||||||
tool_input: { command: 'echo cm0gLXJmIC9ldGM= | base64 -d | bash' },
|
// Blob encoded at runtime: no runnable base64-to-shell line on disk (v8.1.3).
|
||||||
|
tool_input: { command: `echo ${Buffer.from('rm -rf /etc').toString('base64')} | base64 -d | bash` },
|
||||||
});
|
});
|
||||||
assert.equal(result.code, 2);
|
assert.equal(result.code, 2);
|
||||||
assert.match(result.stderr, /base64/i);
|
assert.match(result.stderr, /base64/i);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue