34 lines
3.6 KiB
JavaScript
34 lines
3.6 KiB
JavaScript
import { inspect } from './policy.mjs';
|
|
const blocked=ruleId=>({decision:'block',ruleId});
|
|
const allowed=()=>({decision:'allow',ruleId:null});
|
|
const normalize=text=>text.normalize('NFKC').toLowerCase().replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/g,'').replace(/[`^]/g,'');
|
|
// Deterministic indicators of remote/obfuscated shell execution, not a command parser or malware proof.
|
|
export function detectClickFix(text){
|
|
if(typeof text!=='string'||Buffer.byteLength(text)>1048576||text.includes('\0'))return blocked('clipboard-uninspectable');
|
|
const values=[normalize(text)];try{values.push(normalize(decodeURIComponent(text)));}catch{}
|
|
for(const value of values){
|
|
const command=value.replace(/["']/g,'');
|
|
if(/\b(?:powershell|pwsh)(?:\.exe)?\b[\s\S]{0,512}?\s-(?:e|ec|en|enc|enco|encod|encode|encoded|encodedc|encodedco|encodedcom|encodedcomm|encodedcomma|encodedcomman|encodedcommand)(?:\s+|:|=)[a-z0-9+/]+={0,2}/.test(command))return blocked('clickfix-encoded-powershell');
|
|
const remote=/https?:\/\//.test(command),download=/\b(?:invoke-webrequest|invoke-restmethod|iwr|irm)\b|\.download(?:string|file)\s*\(/.test(command),execute=/\b(?:invoke-expression|iex)\b/.test(command);
|
|
if(remote&&download&&execute)return blocked('clickfix-powershell-remote-execution');
|
|
if(/\bmshta(?:\.exe)?\s+(?:https?:\/\/|(?:vbscript|javascript):)/.test(command))return blocked('clickfix-mshta-execution');
|
|
if(/\brundll32(?:\.exe)?\b[\s\S]{0,512}?javascript:[\s\S]{0,512}?(?:runhtmlapplication|getobject)/.test(command))return blocked('clickfix-rundll32-script');
|
|
if(/\bregsvr32(?:\.exe)?\b[\s\S]{0,512}?\/i:https?:\/\//.test(command))return blocked('clickfix-regsvr32-remote-script');
|
|
if(remote&&/\b(?:curl|wget)(?:\.exe)?\b[^\n;]{0,1024}\|\s*(?:sudo\s+)?(?:sh|bash|zsh|pwsh|powershell)(?:\.exe)?\b/.test(command))return blocked('clickfix-download-pipe-shell');
|
|
if(remote&&/\b(?:sh|bash|zsh)\s+-c\s+\$\([^\n]{0,1024}\b(?:curl|wget)\b/.test(command))return blocked('clickfix-shell-download-substitution');
|
|
if(remote&&(download||/\b(?:curl|wget|certutil|bitsadmin)(?:\.exe)?\b/.test(command))&&/(?:&&|;|\n)\s*(?:(?:start|start-process|sh|bash|\.\/|\.\\)\s*)?[^\s;\n]{1,200}\.(?:exe|ps1|bat|cmd|sh)\b/.test(command))return blocked('clickfix-download-then-execute');
|
|
if(remote&&/\b(?:python|python3)(?:\.exe)?\s+-c\b/.test(command)&&/\b(?:exec|eval)\s*\(/.test(command)&&/\b(?:urllib|requests)\b/.test(command))return blocked('clickfix-python-remote-execution');
|
|
}
|
|
return allowed();
|
|
}
|
|
export function inspectClipboard(bytes,policy,operation){
|
|
if(!['copy','cut','paste'].includes(operation))return blocked('clipboard-operation-denied');
|
|
if(!policy||!Number.isFinite(Date.parse(policy.expiresAt))||Date.parse(policy.expiresAt)<=Date.now())return blocked('clipboard-policy-expired');
|
|
const settings=policy.security?.clipboard;if(!settings||settings.failClosed!==true)return blocked('clipboard-policy-unavailable');
|
|
if(settings.enabled&&!settings[`allow${operation[0].toUpperCase()}${operation.slice(1)}`])return blocked(`clipboard-${operation}-disabled`);
|
|
let data,text;try{if(!Buffer.isBuffer(bytes)&&!(bytes instanceof Uint8Array)&&typeof bytes!=='string')throw Error();data=Buffer.from(bytes);if(data.length>Math.min(settings.maxTextBytes,policy.dlp.maxBytes))return blocked('clipboard-size-limit');text=new TextDecoder('utf-8',{fatal:true}).decode(data);if(text.includes('\0'))throw Error();}catch{return blocked('clipboard-uninspectable');}
|
|
const verdict=inspect(data,policy);if(verdict.decision!=='allow')return verdict;
|
|
return settings.enabled&&settings.blockSuspiciousCommands?detectClickFix(text):allowed();
|
|
}
|
|
|