This commit is contained in:
taqin
2026-04-19 21:10:40 +07:00
parent 5fdd214fdc
commit 27381d4e37
211 changed files with 53571 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import Terminal from "../Terminal.vue";
import Prompt from "../Prompt.vue";
import { useTerminal } from "../../composables/useTerminal";
interface AdminHit {
url: string;
status: number;
size: number;
title?: string;
platform?: string;
login_form: boolean;
auth_header?: string;
redirect?: string;
}
const baseUrl = ref("https://example.com");
const useBuiltin = ref(true);
const extraPaths = ref("");
const acceptStatusStr = ref("200,301,302,401,403");
const concurrency = ref(40);
const timeoutMs = ref(5000);
const followRedirects = ref(false);
const running = ref(false);
const progress = ref({ done: 0, total: 0 });
const hits = ref<AdminHit[]>([]);
const log = useTerminal();
const unlistens: UnlistenFn[] = [];
const builtinCount = ref(0);
async function loadCount() {
try {
const list: string[] = await invoke("admin_finder_wordlist");
builtinCount.value = list.length;
} catch {}
}
async function run() {
if (running.value) return;
running.value = true;
hits.value = [];
progress.value = { done: 0, total: 0 };
log.clear();
const extras = extraPaths.value.split(/[\s\n,]+/).map(s => s.trim()).filter(Boolean);
const statuses = acceptStatusStr.value.split(/[,\s]+/).map(s => parseInt(s, 10)).filter(n => !isNaN(n));
log.info(`target: ${baseUrl.value}`);
log.info(`paths: builtin=${useBuiltin.value ? builtinCount.value : 0} + ${extras.length} extra`);
unlistens.push(await listen<string>("adminfind:status", (e) => log.dim(String(e.payload))));
unlistens.push(await listen<{ done: number; total: number }>("adminfind:progress", (e) => { progress.value = e.payload; }));
unlistens.push(await listen<AdminHit>("adminfind:hit", (e) => {
const h = e.payload;
hits.value.push(h);
const flags: string[] = [];
if (h.login_form) flags.push("LOGIN");
if (h.auth_header) flags.push("AUTH");
if (h.platform) flags.push(h.platform);
const flagStr = flags.length ? `[${flags.join("|")}]` : "";
const line = `${String(h.status).padEnd(4)} ${h.url} ${flagStr}${h.title ? ` "${h.title.slice(0, 50)}"` : ""}`;
if (h.login_form || h.auth_header) log.hit(line);
else log.valid(line);
}));
try {
await invoke("admin_finder_run", {
req: {
base_url: baseUrl.value.trim(),
extra_paths: extras,
use_builtin: useBuiltin.value,
accept_status: statuses,
concurrency: concurrency.value,
timeout_ms: timeoutMs.value,
follow_redirects: followRedirects.value,
},
});
const logins = hits.value.filter(h => h.login_form || h.auth_header).length;
log.ok(`done: ${hits.value.length} responses, ${logins} login panel(s) found`);
} catch (e: any) {
log.err(String(e));
} finally {
running.value = false;
cleanup();
}
}
function cleanup() { while (unlistens.length) { const u = unlistens.pop(); if (u) u(); } }
onMounted(loadCount);
onUnmounted(cleanup);
</script>
<template>
<div class="module">
<div class="form">
<Prompt label="target" v-model="baseUrl" placeholder="https://target.com" />
<div class="row">
<label class="mini wide"><span>status</span><input v-model="acceptStatusStr" placeholder="200,301,302,401,403" /></label>
</div>
<div class="row">
<label class="toggle"><input type="checkbox" v-model="useBuiltin" /><span>builtin ({{ builtinCount }})</span></label>
<label class="toggle"><input type="checkbox" v-model="followRedirects" /><span>follow</span></label>
<label class="mini"><span>conc</span><input v-model.number="concurrency" type="number" min="1" max="200" /></label>
<label class="mini"><span>t/out</span><input v-model.number="timeoutMs" type="number" min="500" max="30000" /></label>
</div>
<details class="adv">
<summary>extra paths (optional)</summary>
<textarea class="term-input" v-model="extraPaths" rows="3" spellcheck="false" placeholder="one path per line, e.g. panel/admin, custom-admin-path" />
</details>
<button class="exec" :disabled="running" @click="run">{{ running ? "[ probing... ]" : "> find admin" }}</button>
<div v-if="progress.total" class="bar">
<div class="bar-fill" :style="{ width: (progress.done / progress.total) * 100 + '%' }" />
<span class="bar-text">{{ progress.done }} / {{ progress.total }}</span>
</div>
</div>
<Terminal :lines="log.lines.value" title="admin-finder // panel discovery" @clear="log.clear()" />
</div>
</template>
<style scoped>
.adv { border: 1px solid var(--border); background: var(--bg-panel); padding: 4px 8px; }
.adv summary { cursor: pointer; font-size: 10px; color: var(--fg-dim); text-transform: uppercase; letter-spacing: 0.1em; padding: 2px 0; }
.adv summary:hover { color: var(--fg); }
.adv > *:not(summary) { margin-top: 6px; }
</style>