Add fake admin decoy and neon redesign
This commit is contained in:
@@ -0,0 +1,469 @@
|
||||
package ru.serega6531.packmate.security;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FakeAdminResponder {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
private final List<String> encodedImages;
|
||||
|
||||
public FakeAdminResponder() {
|
||||
this.encodedImages = loadImages();
|
||||
}
|
||||
|
||||
private List<String> loadImages() {
|
||||
try {
|
||||
Resource[] resources = new PathMatchingResourcePatternResolver()
|
||||
.getResources("classpath:/static/fake/images/*");
|
||||
|
||||
List<String> images = Arrays.stream(resources)
|
||||
.map(resource -> {
|
||||
try {
|
||||
String contentType = URLConnection.guessContentTypeFromName(resource.getFilename());
|
||||
if (contentType == null) {
|
||||
contentType = "image/jpeg";
|
||||
}
|
||||
byte[] raw = StreamUtils.copyToByteArray(resource.getInputStream());
|
||||
return "data:%s;base64,%s".formatted(
|
||||
contentType,
|
||||
Base64.getEncoder().encodeToString(raw));
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to load fake admin image {}", resource.getFilename(), e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
if (images.isEmpty()) {
|
||||
log.warn("No images found for fake admin fun mode");
|
||||
}
|
||||
|
||||
return images;
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to load fake admin images", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public String funPageHtml() {
|
||||
String phrases = toJson(getFunPhrases());
|
||||
String images = toJson(encodedImages);
|
||||
return """
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>0xb00b5 team Packmate // fake funwall</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=JetBrains+Mono:wght@500&display=swap');
|
||||
:root {
|
||||
--bg: #050512;
|
||||
--accent: #59f3ff;
|
||||
--accent-2: #ff5fd2;
|
||||
--glass: rgba(9, 19, 45, 0.75);
|
||||
--grid: rgba(89, 243, 255, 0.25);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background: radial-gradient(circle at 20% 20%, rgba(255, 95, 210, 0.18), transparent 25%), radial-gradient(circle at 80% 0%, rgba(89, 243, 255, 0.2), transparent 30%), linear-gradient(135deg, #04040d 0%, #0a1024 45%, #050512 100%);
|
||||
color: #e9f7ff;
|
||||
font-family: 'Press Start 2P', 'JetBrains Mono', monospace;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 18px 60px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.grid-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: linear-gradient(var(--grid) 1px, transparent 1px), linear-gradient(90deg, var(--grid) 1px, transparent 1px);
|
||||
background-size: 80px 80px;
|
||||
mask-image: radial-gradient(circle at 50% 20%, rgba(0,0,0,.8), transparent 75%);
|
||||
opacity: 0.5;
|
||||
z-index: 0;
|
||||
}
|
||||
.shell {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(1040px, 100%);
|
||||
background: var(--glass);
|
||||
border: 1px solid rgba(89, 243, 255, 0.4);
|
||||
box-shadow: 0 0 40px rgba(89, 243, 255, 0.25), 0 0 24px rgba(255, 95, 210, 0.2);
|
||||
border-radius: 18px;
|
||||
padding: 28px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.shell::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 12px;
|
||||
border: 1px dashed rgba(255, 95, 210, 0.25);
|
||||
border-radius: 14px;
|
||||
pointer-events: none;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 20px;
|
||||
font-size: 18px;
|
||||
letter-spacing: 1.5px;
|
||||
text-shadow: 0 0 8px rgba(89, 243, 255, 0.8);
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 8px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 95, 210, 0.35);
|
||||
color: #ffcdf4;
|
||||
box-shadow: inset 0 0 10px rgba(255, 95, 210, 0.35), 0 0 10px rgba(255, 95, 210, 0.35);
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 420px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(89, 243, 255, 0.35);
|
||||
background: radial-gradient(circle at 30% 30%, rgba(255, 95, 210, 0.08), transparent 40%), #0b122a;
|
||||
box-shadow: inset 0 0 30px rgba(0,0,0,0.35), 0 0 18px rgba(89, 243, 255, 0.12);
|
||||
}
|
||||
.typed {
|
||||
margin-top: 22px;
|
||||
font-size: 14px;
|
||||
min-height: 24px;
|
||||
letter-spacing: 0.8px;
|
||||
color: #aaf6ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
#typed-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
background: #aaf6ff;
|
||||
animation: blink 0.8s infinite;
|
||||
height: 14px;
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0.2; }
|
||||
}
|
||||
.footer-note {
|
||||
margin-top: 18px;
|
||||
font-size: 11px;
|
||||
color: rgba(233, 247, 255, 0.65);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="grid-bg"></div>
|
||||
<div class="shell">
|
||||
<h1>0xb00b5 team Packmate | admin:admin illusion</h1>
|
||||
<div class="badge">mode: FUN // @danosito</div>
|
||||
<canvas id="snap-canvas"></canvas>
|
||||
<div class="typed">
|
||||
<span id="typed-text"></span><span class="cursor"></span>
|
||||
</div>
|
||||
<div class="footer-note">Real packets stay locked. This is your glitch gallery.</div>
|
||||
</div>
|
||||
<script>
|
||||
const phrases = %s;
|
||||
const images = %s;
|
||||
const canvas = document.getElementById('snap-canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const textEl = document.getElementById('typed-text');
|
||||
|
||||
const pick = (list) => list[Math.floor(Math.random() * list.length)];
|
||||
|
||||
function renderImage() {
|
||||
const chosen = (images && images.length) ? pick(images) : '';
|
||||
if (!chosen) {
|
||||
ctx.fillStyle = '#0b122a';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
return;
|
||||
}
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const maxW = 960;
|
||||
const scale = Math.min(maxW / img.width, 1);
|
||||
const w = img.width * scale;
|
||||
const h = img.height * scale;
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.drawImage(img, 0, 0, w, h);
|
||||
setTimeout(() => snapAway(), 5000);
|
||||
};
|
||||
img.src = chosen;
|
||||
}
|
||||
|
||||
function snapAway() {
|
||||
let ticks = 0;
|
||||
const interval = setInterval(() => {
|
||||
for (let i = 0; i < 48; i++) {
|
||||
const x = Math.random() * canvas.width;
|
||||
const y = Math.random() * canvas.height;
|
||||
const size = 4 + Math.random() * 10;
|
||||
ctx.clearRect(x, y, size, size);
|
||||
}
|
||||
ticks++;
|
||||
if (ticks > 80) {
|
||||
clearInterval(interval);
|
||||
setTimeout(renderImage, 400);
|
||||
}
|
||||
}, 28);
|
||||
}
|
||||
|
||||
function typewriter() {
|
||||
const phrase = pick(phrases);
|
||||
textEl.textContent = '';
|
||||
let i = 0;
|
||||
const typeDelay = phrase.length ? 5000 / phrase.length : 120;
|
||||
|
||||
const typeInterval = setInterval(() => {
|
||||
textEl.textContent += phrase.charAt(i);
|
||||
i++;
|
||||
if (i >= phrase.length) {
|
||||
clearInterval(typeInterval);
|
||||
setTimeout(() => erase(phrase), 3000);
|
||||
}
|
||||
}, typeDelay);
|
||||
}
|
||||
|
||||
function erase(phrase) {
|
||||
const eraseDelay = phrase.length ? 2000 / phrase.length : 80;
|
||||
const eraser = setInterval(() => {
|
||||
textEl.textContent = textEl.textContent.slice(0, -1);
|
||||
if (!textEl.textContent.length) {
|
||||
clearInterval(eraser);
|
||||
setTimeout(typewriter, 200);
|
||||
}
|
||||
}, eraseDelay);
|
||||
}
|
||||
|
||||
renderImage();
|
||||
typewriter();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
""".formatted(phrases, images);
|
||||
}
|
||||
|
||||
public String fakePacketsHtml() {
|
||||
return """
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>0xb00b5 team Packmate // fake packets</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=JetBrains+Mono:wght@500&display=swap');
|
||||
:root {
|
||||
--bg: #04040f;
|
||||
--accent: #ff5fd2;
|
||||
--accent-2: #59f3ff;
|
||||
--glass: rgba(8, 12, 30, 0.85);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(160deg, #04040f 0%, #06081a 50%, #0b1535 100%);
|
||||
color: #e9f7ff;
|
||||
font-family: 'Press Start 2P', 'JetBrains Mono', monospace;
|
||||
padding: 34px 18px 60px;
|
||||
}
|
||||
.matrix {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 14px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.banner {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(89, 243, 255, 0.35);
|
||||
box-shadow: 0 0 12px rgba(89, 243, 255, 0.25);
|
||||
background: rgba(6, 18, 40, 0.8);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card {
|
||||
background: var(--glass);
|
||||
border: 1px solid rgba(255, 95, 210, 0.35);
|
||||
border-radius: 16px;
|
||||
padding: 12px 14px;
|
||||
box-shadow: 0 0 18px rgba(255, 95, 210, 0.18);
|
||||
}
|
||||
.card h3 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
color: #ffb3f1;
|
||||
}
|
||||
.card .meta {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.5px;
|
||||
color: rgba(233, 247, 255, 0.8);
|
||||
}
|
||||
.storm {
|
||||
background: rgba(6, 12, 28, 0.9);
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(89, 243, 255, 0.35);
|
||||
box-shadow: inset 0 0 24px rgba(0,0,0,0.4), 0 0 14px rgba(89, 243, 255, 0.2);
|
||||
padding: 16px;
|
||||
height: 320px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.packet-line {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.5px;
|
||||
color: #c8f7ff;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px dashed rgba(255, 95, 210, 0.15);
|
||||
pointer-events: none;
|
||||
opacity: 0.85;
|
||||
animation: flicker 1.5s infinite;
|
||||
}
|
||||
.packet-line:nth-child(2n) { color: #aee3ff; }
|
||||
.packet-line:nth-child(3n) { color: #ffb3f1; }
|
||||
.packet-line.ghosted { opacity: 0.3; }
|
||||
@keyframes flicker {
|
||||
0% { text-shadow: 0 0 6px rgba(89, 243, 255, 0.35); }
|
||||
50% { text-shadow: 0 0 12px rgba(255, 95, 210, 0.3); }
|
||||
100% { text-shadow: 0 0 6px rgba(89, 243, 255, 0.35); }
|
||||
}
|
||||
.footer {
|
||||
margin-top: 16px;
|
||||
font-size: 11px;
|
||||
color: rgba(233, 247, 255, 0.6);
|
||||
}
|
||||
.mute {
|
||||
opacity: 0.02;
|
||||
font-size: 10px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="banner">0xb00b5 team Packmate // decoy panel</div>
|
||||
<div class="matrix" id="service-grid"></div>
|
||||
<div class="storm" id="packet-feed"></div>
|
||||
<div class="footer">admin:admin only gets noise. Real API stays sealed.</div>
|
||||
<div class="mute">You're stupid:)</div>
|
||||
<script>
|
||||
const serviceGrid = document.getElementById('service-grid');
|
||||
const packetFeed = document.getElementById('packet-feed');
|
||||
let packetId = Math.floor(Math.random() * 100000) + 1;
|
||||
let services = [];
|
||||
|
||||
fetch('/api/fake/services')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
services = data;
|
||||
renderServices();
|
||||
startStorm();
|
||||
})
|
||||
.catch(() => {
|
||||
services = [{ name: 'ghost', port: 0, packetKind: 'tcp' }];
|
||||
renderServices();
|
||||
startStorm();
|
||||
});
|
||||
|
||||
function renderServices() {
|
||||
serviceGrid.innerHTML = '';
|
||||
services.forEach(svc => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
card.innerHTML = `
|
||||
<h3>${svc.name} #${svc.port}</h3>
|
||||
<div class="meta">packets: ${svc.packetKind || 'tcp'} stream</div>
|
||||
<div class="meta">interface: locked</div>
|
||||
`;
|
||||
serviceGrid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function spawnPacket() {
|
||||
if (!services.length) {
|
||||
return;
|
||||
}
|
||||
const svc = services[Math.floor(Math.random() * services.length)];
|
||||
const line = document.createElement('div');
|
||||
line.className = 'packet-line';
|
||||
const protocol = (svc.packetKind || 'tcp').toUpperCase();
|
||||
line.textContent = `#${packetId} ${protocol} // :${svc.port} ${svc.name} // payload ${Math.floor(Math.random() * 1800)}b`;
|
||||
packetId++;
|
||||
|
||||
packetFeed.prepend(line);
|
||||
while (packetFeed.children.length > 140) {
|
||||
packetFeed.removeChild(packetFeed.lastChild);
|
||||
}
|
||||
|
||||
setTimeout(() => line.classList.add('ghosted'), 1200);
|
||||
}
|
||||
|
||||
function startStorm() {
|
||||
setInterval(spawnPacket, 50);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
}
|
||||
|
||||
private List<String> getFunPhrases() {
|
||||
return List.of(
|
||||
"Here's the flag. Are you ready? here it goes... Wait, no.",
|
||||
"Wanna see the flag? send yours to @danosito:)",
|
||||
"Hey, why are you here? go pentest our services",
|
||||
"Hmmm i think <script>alert(\"You're stupid\")</script> might work..",
|
||||
"Bip, boop, here was packet but codex ate it",
|
||||
"Our LLM tockens ran out. Maybe you could give us some:)?",
|
||||
":(){ :|:& };:",
|
||||
"i think creds are admin:admin but i'm not sure...",
|
||||
"Try eternalBlue, i think it would work",
|
||||
"I think i defended this page well enough, here is flag: LLMDELETEDTHEFLAG=",
|
||||
"Go open ida pro and reverse this text",
|
||||
"I would give you our flags for free, but you are a bad person:(",
|
||||
"b00b5 is not a fresh meat:(",
|
||||
"marcus, send your packmate credits pls",
|
||||
"Marcus, fuck off",
|
||||
"<a href=\\"https://youtu.be/rrw-Pv3rc0E?si=-ZQmhZVxh4HF6luD\\">Your special guide to get flag!</a>"
|
||||
);
|
||||
}
|
||||
|
||||
private String toJson(List<String> data) {
|
||||
try {
|
||||
return mapper.writeValueAsString(data);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("Failed to convert data to json for fake admin", e);
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user