ahooks#useGetState TypeScript Examples
The following examples show how to use
ahooks#useGetState.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useLocalCacheState.ts From yakit with GNU Affero General Public License v3.0 | 6 votes |
// 关于获取函数还有点问题,暂不可用
export default function useLocalCacheState(keyword: string) {
const [cache, setCache, getCache] = useGetState<string>()
const time = useRef<any>(null)
useEffect(() => {
ipcRenderer
.invoke("get-value", keyword)
.then((res: any) => setCache(res))
.catch(() => {})
return () => {
ipcRenderer.invoke("set-value", keyword, getCache())
}
}, [])
const setLocalCache = (value?: string) => {
if (time.current) {
clearTimeout(time.current)
time.current = null
}
time.current = setTimeout(() => {
setCache(value)
ipcRenderer.invoke("set-value", keyword, value)
}, 1000)
}
const getLocalCache = () => {
return getCache()
}
return [cache, {setLocalCache, getLocalCache}] as const
}
Example #2
Source File: ReportViewerPage.tsx From yakit with GNU Affero General Public License v3.0 | 6 votes |
ReportViewerPage: React.FC<ReportViewerPageProp> = (props) => {
const [_, setReport, getReport] = useGetState<Report>();
return <>
<ResizeBox
isVer={false}
firstNode={<ReportList onClick={setReport} selectedId={getReport()?.Id}/>}
firstMinSize={"320px"}
firstRatio={"320px"}
secondNode={(() => {
return <ReportViewer id={getReport()?.Id || 0}/>
})()}
/>
</>
}
Example #3
Source File: exporter.tsx From yakit with GNU Affero General Public License v3.0 | 5 votes |
GeneralExporter: React.FC<GeneralExporterProp> = (props) => {
const [token, setToken] = useState(randomString(30));
const [paths, setPaths, getPaths] = useGetState<string[]>([]);
useEffect(() => {
if (!token) {
return
}
ipcRenderer.on(`${token}-data`, (_, data: { FilePath: string }) => {
const origin = getPaths();
origin.push(data.FilePath)
setPaths(origin.map(v => v))
})
ipcRenderer.on(`${token}-end`, () => {
info("导出结束")
})
ipcRenderer.on(`${token}-error`, (_, e) => {
})
const {JsonOutput, CSVOutput, DirName, FilePattern} = props;
ipcRenderer.invoke("ExtractDataToFile", {
token,
params: {JsonOutput, CSVOutput, DirName, FilePattern}
}).then(() => {
info("发送生成文件配置成功...")
})
props.Data.forEach(value => {
ipcRenderer.invoke("ExtractDataToFile", {
token,
params: {Data: value}
}).then(() => {
})
})
ipcRenderer.invoke("ExtractDataToFile", {token, params: {Finished: true}})
return () => {
ipcRenderer.removeAllListeners(`${token}-data`)
ipcRenderer.removeAllListeners(`${token}-error`)
ipcRenderer.removeAllListeners(`${token}-end`)
}
}, [token])
return <AutoCard title={"获取生成的文件"}>
<Space direction={"vertical"}>
{paths.map(i => {
return <Button
type={"link"}
onClick={() => {
openABSFileLocated(i)
}}
>{i}</Button>
})}
</Space>
</AutoCard>
}
Example #4
Source File: SimplePluginList.tsx From yakit with GNU Affero General Public License v3.0 | 5 votes |
SimplePluginList: React.FC<SimplePluginListProp> = React.memo((props) => {
const [scripts, setScripts, getScripts] = useGetState<YakScript[]>([])
const [total, setTotal] = useState(0)
const [listNames, setListNames] = useState<string[]>([...(props.initialSelected || [])]);
// const [params, setParams] = useState<{ ScriptNames: string[] }>({ScriptNames: [...props.initialSelected || []]})
const [pluginLoading, setPluginLoading] = useState<boolean>(false)
const allSelectYakScript = useMemoizedFn((flag: boolean) => {
if (flag) {
const newSelected = [...scripts.map((i) => i.ScriptName), ...listNames]
setListNames([...newSelected.filter((e, index) => newSelected.indexOf(e) === index)])
} else {
setListNames([])
}
})
const selectYakScript = useMemoizedFn((y: YakScript) => {
listNames.push(y.ScriptName)
setListNames([...listNames])
})
const unselectYakScript = useMemoizedFn((y: YakScript) => {
const names = listNames.splice(listNames.indexOf(y.ScriptName), 1);
setListNames([...listNames])
})
useEffect(() => {
if (props.onSelected) {
props.onSelected([...listNames])
}
}, [listNames])
const search = useMemoizedFn((searchParams?: { limit: number; keyword: string }) => {
const {limit, keyword} = searchParams || {}
setPluginLoading(true)
queryYakScriptList(
props.pluginTypes ? props.pluginTypes : "",
(data, total) => {
setTotal(total || 0)
setScripts(data)
setListNames([...(data || []).filter(i => i.IsGeneralModule).map(i => i.ScriptName)])
},
() => setTimeout(() => setPluginLoading(false), 300),
limit || 200,
undefined,
keyword || "",
props.initialQuery,
)
})
useEffect(() => {
search()
}, [useDebounce(props.initialQuery, {wait: 500})])
return <PluginList
readOnly={props.readOnly}
bordered={props.bordered}
loading={pluginLoading}
lists={(scripts || []).sort((a: YakScript, b: YakScript) => {
return (b.IsGeneralModule ? 1 : 0) - (a.IsGeneralModule ? 1 : 0)
})}
disabled={props.disabled}
getLists={getScripts}
total={total}
selected={listNames}
allSelectScript={allSelectYakScript}
selectScript={selectYakScript}
unSelectScript={unselectYakScript}
search={search}
title={props?.verbose || "选择插件"}
bodyStyle={{
padding: "0 4px",
overflow: "hidden"
}}
/>
})
Example #5
Source File: PluginOperator.tsx From yakit with GNU Affero General Public License v3.0 | 5 votes |
OutputPluginForm: React.FC<OutputPluginFormProp> = React.memo((props) => {
const [_, setLocalPath, getLocalPath] = useGetState("");
const [pluginDirName, setPluginDirName, getPluginDirName] = useGetState("");
useEffect(() => {
getValue("YAKIT_DEFAULT_LOAD_LOCAL_PATH").then(e => {
if (e) {
setLocalPath(e)
}
})
}, [])
return <>
<Form onSubmitCapture={e => {
e.preventDefault()
ipcRenderer
.invoke("ExportYakScript", {
YakScriptId: props.YakScriptId,
OutputDir: getLocalPath(),
OutputPluginDir: getPluginDirName(),
})
.then((data: { OutputDir: string }) => {
showModal({
title: "导出成功!",
content: (
<>
<Space direction={"vertical"}>
<CopyableField text={data.OutputDir}/>
<Button
type={"link"}
onClick={() => {
openABSFile(data.OutputDir)
}}
>
在文件夹中打开
</Button>
</Space>
</>
)
})
})
.catch((e: any) => {
failed(`导出失败: ${e}`)
})
}}>
<InputItem
label={"本地仓库路径"}
help={"可在【导出】或仓库配置中配置"}
value={getLocalPath()}
setValue={setLocalPath}
required={true}
/>
<InputItem
label={"插件文件夹名"}
help={"插件文件夹名,尽量精简,无特殊字符"}
value={getPluginDirName()}
setValue={setPluginDirName}
required={true}
/>
<Form.Item colon={false} label={" "}>
<Button type="primary" htmlType="submit"> 导出到目标路径 </Button>
</Form.Item>
</Form>
</>
})
Example #6
Source File: BatchExecutorPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
BatchTaskViewer: React.FC<BatchTaskViewerProp> = React.memo((props) => {
const [tasks, setTasks, getTasks] = useGetState<BatchTask[]>([]);
const containerRef = useRef();
const listRef = useRef();
const [height, setHeight] = useState(300);
const [list, scrollTo] = useVirtualList<BatchTask>(getTasks(), {
containerTarget: containerRef,
wrapperTarget: listRef,
itemHeight: 40,
overscan: 5,
})
const [xtermRef, setXtermRef, getXtermRef] = useGetState<any>(null)
// 转换task内的result数据
const convertTask = (task: BatchTask) => {
// @ts-ignore
const results: ExecResult[] = task.Results.filter((item) => !!item.Result).map((item) => item.Result)
const messages: ExecResultMessage[] = []
for (let item of results) {
if (!item.IsMessage) continue
try {
const raw = item.Message
const obj: ExecResultMessage = JSON.parse(Buffer.from(raw).toString("utf8"))
messages.push(obj)
} catch (e) {
console.error(e)
}
}
return messages
}
const statusTag = (task: BatchTask) => {
const messages: ExecResultMessage[] = convertTask(task)
const logs: ExecResultLog[] = messages
.filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[]
const haveResult = logs.filter((i) => ["json", "success"].includes((i?.level || "").toLowerCase())).length > 0
return haveResult ? <Tag color={"red"}>HIT</Tag> : "-"
}
const details = (task: BatchTask) => {
const infos: ExecResultMessage[] = convertTask(task)
const messages: ExecResultMessage[] = []
const featureMessages: ExecResultMessage[] = []
const featureTypes: ExecResultMessage[] = []
const processKVPair: Map<string, number> = new Map<string, number>()
const statusKVPair: Map<string, CacheStatusCardProps> = new Map<string, CacheStatusCardProps>()
for (let item of infos) {
try {
// 处理 Process KVPair
if (item.type === "progress") {
const processData = item.content as ExecResultProgress
if (processData && processData.id) {
processKVPair.set(
processData.id,
Math.max(processKVPair.get(processData.id) || 0, processData.progress)
)
}
return
}
// 处理 log feature-status-card-data
const logData = item.content as ExecResultLog
if (item.type === "log" && logData.level === "feature-status-card-data") {
try {
const obj = JSON.parse(logData.data)
const {id, data, tags} = obj
const {timestamp} = logData
const originData = statusKVPair.get(id)
if (originData && originData.Timestamp > timestamp) {
return
}
statusKVPair.set(id, {
Id: id,
Data: data,
Timestamp: timestamp,
Tags: Array.isArray(tags) ? tags : []
})
} catch (e) {
}
return
}
if (item.type === "log" && logData.level === "json-feature") {
try {
featureTypes.unshift(item)
} catch (e) {
}
return
}
if (item.type === "log" && logData.level === "feature-table-data") {
try {
featureMessages.unshift(item)
} catch (e) {
}
return
}
messages.unshift(item)
// 只缓存 100 条结果(日志类型 + 数据类型)
if (messages.length > 100) {
messages.pop()
}
} catch (e) {
}
}
let results = messages.filter((i) => i.type === "log").map((i) => i.content as ExecResultLog)
let featureResults = featureMessages
.filter((i) => i.type === "log")
.map((i) => i.content as ExecResultLog)
.filter((i) => i.data !== "null")
let featureType = featureTypes
.filter((i) => i.type === "log")
.map((i) => i.content as ExecResultLog)
.filter((i) => i.data !== "null")
const processes: ExecResultProgress[] = []
processKVPair.forEach((value, id) => {
processes.push({id: id, progress: value})
})
const cacheStatusKVPair: { [x: string]: StatusCardInfoProps } = {}
const statusCards: StatusCardProps[] = []
statusKVPair.forEach((value) => {
const item = JSON.parse(JSON.stringify(value))
item.Tag = item.Tags[0] || ""
delete item.Tags
statusCards.push(item)
})
statusCards.sort((a, b) => a.Id.localeCompare(b.Id))
for (let item of statusCards) {
if (item.Tag) {
if (cacheStatusKVPair[item.Tag]) {
cacheStatusKVPair[item.Tag].info.push(item)
} else {
cacheStatusKVPair[item.Tag] = {tag: item.Tag, info: [item]}
}
} else {
cacheStatusKVPair[item.Id] = {tag: item.Id, info: [item]}
}
}
return (
<PluginResultUI
loading={false}
progress={processes.sort((a, b) => a.id.localeCompare(b.id))}
results={results}
featureType={featureType}
feature={featureResults}
statusCards={Object.values(cacheStatusKVPair)}
onXtermRef={setXtermRef}
></PluginResultUI>
)
}
useEffect(() => {
if (!props.checked) setTasks(props.tasks)
}, [props.tasks])
useEffect(() => {
if (props.checked) {
const filterTasks: BatchTask[] = getTasks()
.filter(item => item.Results.length !== 0)
.filter(item =>
(convertTask(item).filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[])
.filter((i) => ["json", "success"]
.includes((i?.level || "").toLowerCase())).length > 0
)
setTasks(filterTasks)
} else {
setTasks(props.tasks)
}
}, [props.checked])
return <AutoCard bodyStyle={{padding: 0, overflow: "hidden"}} style={{height: "100%"}}>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setHeight(height)
}}
handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}
/>
<div ref={containerRef as Ref<any>} style={{height: height, overflow: "auto"}}>
<div ref={listRef as Ref<any>}>
{list.map(i => {
return <div className="history-list-task-opt" key={i.data.TaskId}>
<Text ellipsis={{tooltip: true}} copyable={true}
style={{width: 300}}>{`${i.data.Target} / ${i.data.PoC.ScriptName}`}</Text>
<Divider type='vertical'/>
{statusTag(i.data)}
<Divider type='vertical'/>
<Tag color="green">{formatTimestamp(i.data.CreatedAt)}</Tag>
<Divider type='vertical'/>
<Button type="link" onClick={(e) => {
let m = showDrawer({
title: "poc详情",
keyboard: false,
width: "60%",
onClose: () => {
m.destroy()
},
content: (details(i.data))
})
setTimeout(() => {
// @ts-ignore
const execResults: ExecResult[] = i.data.Results.filter(
(item) => !!item.Result
).map((item) => item.Result)
for (let item of execResults) writeExecResultXTerm(getXtermRef(), item)
}, 500)
}}>详情</Button>
</div>
})}
</div>
</div>
</AutoCard>
})
Example #7
Source File: basic.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
ConfigGlobalReverseButton = React.memo(() => {
const [addr, setAddr, getAddr] = useGetState("");
const [password, setPassword, getPassword] = useGetState("");
const [localIP, setLocalIP, getLocalIP] = useGetState("");
const [ifaces, setIfaces] = useState<NetInterface[]>([]);
const [visible, setVisible] = useState(false);
const [ok, setOk] = useState(false)
const getStatus = useMemoizedFn(() => {
ipcRenderer.invoke("get-global-reverse-server-status").then((r) => {
setOk(r)
saveValue(BRIDGE_ADDR, addr)
saveValue(BRIDGE_SECRET, password)
})
})
const cancel = useMemoizedFn(() => {
ipcRenderer.invoke("cancel-global-reverse-server-status").finally(() => {
getStatus()
})
});
const login = useMemoizedFn(() => {
ipcRenderer.invoke("ConfigGlobalReverse", {
ConnectParams: {Addr: addr, Secret: password},
LocalAddr: localIP,
}).then(() => {
getStatus()
// setVisible(false)
}).catch(e => {
failed(`Config Global Reverse Server failed: ${e}`)
})
})
useEffect(() => {
if (!visible) {
return
}
getStatus()
}, [visible])
// 设置 Bridge
useEffect(() => {
if (!addr) {
getValue(BRIDGE_ADDR).then((data: string) => {
if (!!data) {
setAddr(`${data}`)
}
})
}
if (!password) {
getValue(BRIDGE_SECRET).then((data: string) => {
if (!!data) {
setPassword(`${data}`)
}
})
}
return () => {
cancel()
}
}, [])
// 如果 addr 和 password 都存在,且没有连接,则马上连接一次
useEffect(() => {
// 可见就退出
if (visible) {
return
}
// 如果已经连上就退出
if (ok) {
return
}
if (!!addr && !!password) {
login()
let id = setInterval(() => {
login()
}, 1000)
return () => {
clearInterval(id)
}
}
}, [addr, password, visible, ok])
const updateIface = useMemoizedFn(() => {
ipcRenderer.invoke("AvailableLocalAddr", {}).then((data: { Interfaces: NetInterface[] }) => {
const arr = (data.Interfaces || []).filter(i => i.IP !== "127.0.0.1");
setIfaces(arr)
})
})
useEffect(() => {
if (visible) {
updateIface()
}
}, [visible])
useEffect(() => {
if (ifaces.length === 1) {
setLocalIP(ifaces[0].IP)
}
}, [ifaces])
return <div>
<Button type={"link"}
onClick={() => {
setVisible(true)
}}
>配置全局反连</Button>
<Modal visible={visible}
width={"60%"}
okButtonProps={{hidden: true}}
cancelButtonProps={{hidden: true}}
closable={true}
onCancel={() => {
setVisible(false)
}} afterClose={() => setVisible(false)}>
<Form
style={{marginTop: 20}}
onSubmitCapture={e => {
e.preventDefault()
login()
}} labelCol={{span: 5}} wrapperCol={{span: 14}}>
<InputItem
label={"本地反连 IP"}
value={localIP} disable={ok}
setValue={setLocalIP}
help={<div>
<Button type={"link"} size={"small"} onClick={() => {
updateIface()
}} icon={<ReloadOutlined/>}>
更新 yak 引擎本地 IP
</Button>
</div>}
/>
<Divider orientation={"left"}>公网反连配置</Divider>
<Form.Item label={" "} colon={false}>
<Alert message={<Space direction={"vertical"}>
<div>在公网服务器上运行</div>
<Text code={true} copyable={true}>yak bridge --secret [your-password]</Text>
<div>或</div>
<Text code={true} copyable={true}>
docker run -it --rm --net=host v1ll4n/yak-bridge yak bridge --secret
[your-password]
</Text>
<div>已配置</div>
</Space>}/>
</Form.Item>
<InputItem
label={"Yak Bridge 地址"} value={addr}
setValue={setAddr} disable={ok}
help={"格式 host:port, 例如 cybertunnel.run:64333"}
/>
<InputItem
label={"Yak Bridge 密码"}
setValue={setPassword} value={password}
type={"password"} disable={ok}
help={`yak bridge 命令的 --secret 参数值`}
/>
<Form.Item colon={false} label={" "}>
<Button type="primary" htmlType="submit" disabled={ok}> 配置反连 </Button>
{ok && <Button type="primary" danger={true} onClick={() => {
cancel()
}}> 停止 </Button>}
</Form.Item>
</Form>
</Modal>
</div>
})
Example #8
Source File: RiskStatsTag.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
RiskStatsTag: React.FC<RiskStatsTagProp> = React.memo((props) => {
const [originTotal, setOriginTotal] = useState(0);
const [total, setTotal] = useState(0);
const [originCriticalOrHigh, setOriginCriticalOrHigh, getOriginCriticalOrHigh] = useGetState(0);
const [criticalOrHigh, setCriticalOrHigh] = useState(0);
const [latestRisk, setLatestRisk] = useState<Risk>();
const updateHighOrCritical = useMemoizedFn(() => {
ipcRenderer.invoke("QueryRisks", {
...{Severity: "high,critical"},
Pagination: {Limit: 1, Page: 1, Order: "desc", OrderBy: "updated_at"} as PaginationSchema,
}).then((r: QueryGeneralResponse<Risk>) => {
if ((r?.Data || []).length > 0) {
setLatestRisk(r.Data[0])
}
const total = r?.Total || 0;
setCriticalOrHigh(total)
if (originCriticalOrHigh === 0) {
setOriginCriticalOrHigh(total)
}
}).catch((e) => {
}).finally(() => setTimeout(() => {
// setLoading(false)
}, 300))
})
const updateTotal = useMemoizedFn((after?: () => any) => {
ipcRenderer.invoke("QueryRisks", {
Pagination: {Limit: 1, Page: 1, Order: "desc", OrderBy: "updated_at"} as PaginationSchema,
}).then((r: QueryGeneralResponse<Risk>) => {
const total = r?.Total || 0;
setTotal(total)
if (originTotal === 0) {
setOriginTotal(total)
}
if (after) {
after()
}
}).catch((e) => {
}).finally(() => setTimeout(() => {
// setLoading(false)
}, 300))
})
useEffect(() => {
const update = () => updateTotal(updateHighOrCritical);
update()
let id = setInterval(update, 5000);
return () => {
clearInterval(id);
}
}, [])
const viewAll = useMemoizedFn((severity?: string) => {
return <Button type={"primary"} size={"small"}
onClick={() => {
showDrawer({
title: "Vulnerabilities && Risks",
width: "70%",
content: <>
<RiskTable severity={severity}/>
</>
})
}}
>所有漏洞与风险</Button>
});
const calcCriticalDelta = useMemoizedFn(() => {
if (originCriticalOrHigh <= 0) {
return 0
}
if (criticalOrHigh <= 0) {
return 0
}
if (criticalOrHigh > originCriticalOrHigh) {
return criticalOrHigh - originCriticalOrHigh
}
return 0
})
const calcOriginDelta = useMemoizedFn(() => {
if (originTotal <= 0 || total <= 0) {
return 0
}
if (total > originTotal) {
return total - originTotal
}
return 0
})
const criticalDelta = calcCriticalDelta();
const ordinaryDelta = calcOriginDelta() - criticalDelta;
return <Space size={0}>
<Popover
title={"漏洞与风险计数"}
content={<Space>
<Button onClick={() => {
setOriginTotal(0)
setOriginCriticalOrHigh(0)
}} size={"small"}>标为已读(全部)</Button>
{viewAll("high|critical")}
</Space>}
>
<Tag
color={criticalDelta > 0 ? "red" : undefined}
>高危/严重{criticalDelta > 0 ? `(+${criticalDelta})` : `(无新增)`}
</Tag>
</Popover>
{ordinaryDelta > 0 && props.professionalMode && <Popover
title={"漏洞与风险计数"}
content={<Space>
<Button onClick={() => {
setOriginTotal(0)
setOriginCriticalOrHigh(0)
}} size={"small"}>标为已读(全部)</Button>
{viewAll()}
</Space>}
>
<Tag
color={ordinaryDelta > 0 ? "orange" : undefined}
>中低危/指纹{ordinaryDelta > 0 ? `(+${ordinaryDelta})` : `(无新增)`}
</Tag>
</Popover>}
</Space>
})
Example #9
Source File: RiskTable.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
RiskTable: React.FC<RiskTableProp> = (props) => {
const [response, setResponse] = useState<QueryGeneralResponse<Risk>>({
Data: [],
Pagination: genDefaultPagination(20),
Total: 0
})
const [params, setParams, getParams] = useGetState<QueryRisksParams>({
Severity: props.severity,
Pagination: genDefaultPagination(20)
}
)
const total = response.Total
const pagination = response.Pagination
const page = response.Pagination.Page
const limit = response.Pagination.Limit
const [loading, setLoading] = useState(false)
const [types, setTypes] = useState<FieldNameSelectItem[]>([]);
const [severities, setSeverities] = useState<FieldNameSelectItem[]>([]);
const time = useRef<any>(null)
const updateRiskAndLevel = useMemoizedFn(() => {
ipcRenderer.invoke("QueryAvailableRiskType", {}).then((f: Fields) => {
setTypes(mergeFieldNames(f).sort((a, b) => {
const diff = a.Total - b.Total
if (diff === 0) {
return a.Verbose.localeCompare(b.Verbose)
} else {
return diff
}
}))
})
ipcRenderer.invoke("QueryAvailableRiskLevel", {}).then((i: Fields) => {
setSeverities(mergeFieldNames(i).sort((a, b) => {
const diff = a.Total - b.Total
if (diff === 0) {
return a.Verbose.localeCompare(b.Verbose)
} else {
return diff
}
}))
})
})
const update = useMemoizedFn(
(page?: number, limit?: number, order?: string, orderBy?: string, extraParam?: any) => {
const paginationProps = {
Page: page || 1,
Limit: limit || pagination.Limit
}
setLoading(true)
ipcRenderer
.invoke("QueryRisks", {
...params,
...(extraParam ? extraParam : {}),
Pagination: paginationProps
})
.then((r: QueryGeneralResponse<any>) => {
setResponse(r)
updateRiskAndLevel()
})
.catch((e) => {
failed(`QueryRisks failed: ${e}`)
})
.finally(() => setTimeout(() => setLoading(false), 300))
}
)
const delRisk = useMemoizedFn((hash: string) => {
setLoading(true)
ipcRenderer
.invoke("DeleteRisk", {
Hash: hash
})
.then(() => {
update(1)
})
.catch((e) => {
failed(`DelRisk failed: ${e}`)
})
.finally(() => setTimeout(() => setLoading(false), 300))
})
const filterSelect = useMemoizedFn((type: string, value: string) => {
const relation = {type: "RiskType", severity: "Severity"}
const arr = getParams()[relation[type]] ? getParams()[relation[type]]?.split("|") : []
const flag = arr.filter((item) => value.startsWith(item)) || []
if (flag.length === 0) {
arr?.push(value)
setParams({...getParams(), [relation[type]]: arr?.join("|")})
} else {
const filters = arr?.filter((item) => !value.startsWith(item)) || []
setParams({...getParams(), [relation[type]]: filters.join("|")})
}
if (time.current) {
clearTimeout(time.current)
time.current = null
}
time.current = setTimeout(() => {
update(1)
}, 1000);
})
const isSelected = useMemoizedFn((type: string, value: string) => {
const relation = {type: "RiskType", severity: "Severity"}
const arr = getParams()[relation[type]] ? getParams()[relation[type]]?.split("|") : []
const num = arr.filter((item) => value.startsWith(item))
return num.length !== 0
})
useEffect(() => {
update(1)
}, [])
const showSelectedTag = () => {
const risktypes = getParams().RiskType ? getParams().RiskType?.split("|") : []
const severitys = getParams().Severity ? getParams().Severity?.split("|") : []
const typekind = types.map((item) => {
item.Names = item.Names || []
return item
})
const severitykind = severities.map((item) => {
item.Names = item.Names || []
return item
})
return (
<>
{risktypes?.map((type) => (
<div className="title-selected-tag" key={type}>
<div className="tag-name-style" key={type}>{
(() => {
const result = typekind.filter((item) => {
return item.Names.join(",").startsWith(type)
})
if (result.length > 0) {
return result[0] && result[0].Verbose
}
return ""
})()
}</div>
<div className="tag-del-style" onClick={() => filterSelect("type", type)}>x</div>
</div>
))}
{severitys?.map((severity) => (
<div className="title-selected-tag" key={severity}>
<div className="tag-name-style"
key={severity}>
{
(() => {
const result = severitykind.filter((item) => {
return item.Names.join(",").startsWith(severity)
})
if (result.length > 0) {
return result[0] && result[0].Verbose
}
return severity
})()
}
{/*{severitykind.filter((item) => item.Names.join(",").startsWith(severity))[0].Verbose}*/}
</div>
<div className="tag-del-style" onClick={() => filterSelect("severity", severity)}>x</div>
</div>
))}
</>
)
}
return (
<div className='risk-table-container'>
<div className="container-table">
<Table<Risk>
title={() => {
return (
<div>
<div className="table-title">
<Space>
{"风险与漏洞"}
<Button
size={"small"}
type={"link"}
onClick={() => {
update()
}}
icon={<ReloadOutlined/>}
/>
</Space>
<Space>
<Button
danger={true}
size={"small"}
type={"primary"}
onClick={() => {
let m = showModal({
title: "删除数据选项",
width: "50%",
content: (
<div>
<DeleteRiskForm
types={types}
severities={severities}
onClose={() => {
m.destroy()
update(1)
}}
/>
</div>
)
})
}}
>
删除数据
</Button>
</Space>
</div>
{(!!getParams().Severity || !!getParams().RiskType) &&
<div className="title-header">{showSelectedTag()}</div>}
</div>
)
}}
size={"small"}
bordered={true}
columns={[
{
title: "标题",
render: (i: Risk) => (
<Paragraph style={{maxWidth: 400, marginBottom: 0}} ellipsis={{tooltip: true}}>
{i?.TitleVerbose || i.Title}
</Paragraph>
),
width: 400,
filterIcon: (filtered) => {
return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
},
filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
return (
params &&
setParams && (
<TableFilterDropdownString
label={"搜索关键字"}
params={params}
setParams={setParams}
filterName={"Search"}
confirm={confirm}
setSelectedKeys={setSelectedKeys}
update={update}
/>
)
)
}
},
{
title: "类型",
render: (i: Risk) => i?.RiskTypeVerbose || i.RiskType,
filterIcon: (filtered) => {
return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
},
filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
return (
params &&
setParams && (
<TableFilterDropdownString
label={"搜索类型关键字"}
params={params}
setParams={setParams}
filterName={"RiskType"}
confirm={confirm}
setSelectedKeys={setSelectedKeys}
update={update}
/>
)
)
}
},
{
title: "等级",
render: (i: Risk) => {
const title = TitleColor.filter((item) => item.key.includes(i.Severity || ""))[0]
return (
<span className={title?.value || "title-default"}>
{title ? title.name : i.Severity || "-"}
</span>
)
},
width: 90
},
{
title: "IP",
render: (i: Risk) => i?.IP || "-",
filterIcon: (filtered) => {
return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
},
filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
return (
params &&
setParams && (
<TableFilterDropdownString
label={"搜索网段"}
params={params}
setParams={setParams}
filterName={"Network"}
confirm={confirm}
setSelectedKeys={setSelectedKeys}
update={update}
/>
)
)
}
},
{title: "Token", render: (i: Risk) => i?.ReverseToken || "-"},
{
title: "发现时间",
render: (i: Risk) => <Tag>{i.CreatedAt > 0 ? formatTimestamp(i.CreatedAt) : "-"}</Tag>
},
{
title: "操作",
render: (i: Risk) => (
<Space>
<Button
size='small'
type={"link"}
onClick={() => {
showModal({
width: "80%",
title: "详情",
content: (
<div style={{overflow: "auto"}}>
<RiskDetails info={i}/>
</div>
)
})
}}
>
详情
</Button>
<Button size='small' type={"link"} danger onClick={() => delRisk(i.Hash)}>
删除
</Button>
</Space>
)
}
]}
rowKey={(e) => e.Hash}
loading={loading}
dataSource={response.Data}
pagination={{
current: +page,
pageSize: limit,
showSizeChanger: true,
total: total,
showTotal: (total) => <Tag>Total:{total}</Tag>,
pageSizeOptions: ["5", "10", "20"]
}}
onChange={(pagination, filters, sorter, extra) => {
const action = extra.action
switch (action) {
case "paginate":
const current = pagination.current
update(+page === current ? 1 : current, pagination.pageSize)
return
case "filter":
update()
return
}
}}
/>
</div>
<div className='container-filter-body'>
{severities.length > 0 && <div className='filter-body-opt'>
<div className='opt-header'>漏洞级别</div>
<div className='opt-list'>
{severities.map((item) => {
const value = (item.Names || []).join(",")
return (
<div
key={value}
className={`opt-list-item ${isSelected("severity", value) ? "selected" : ""}`}
onClick={() => filterSelect("severity", value)}
>
<span className='item-name' title={item.Verbose}>
{item.Verbose}
</span>
<span>{item.Total}</span>
</div>
)
})}
</div>
</div>}
<div className="opt-separator"></div>
{types.length > 0 && <div className='filter-body-opt'>
<div className='opt-header'>漏洞/风险类型</div>
<div className='opt-list'>
{types.map((item) => {
const value = (item.Names || []).join(",")
return (
<div
key={value}
className={`opt-list-item ${isSelected("type", value) ? "selected" : ""}`}
onClick={() => filterSelect("type", value)}
>
<span>{item.Verbose}</span>
<span>{item.Total}</span>
</div>
)
})}
</div>
</div>}
</div>
</div>
)
}
Example #10
Source File: ReverseServerPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
ReverseServerPage: React.FC<ReverseServerPageProp> = (props) => {
const [bridge, setBridge] = useState(false);
const [bridgeLoading, setBridgeLoading] = useState(false);
const [bridgeIP, setBridgeIP] = useState<string>("");
const [bridgeAddr, setBridgeAddr] = useState("");
const [bridgeSecret, setBridgeSecret] = useState("");
const [params, setParams] = useState<StartFacadeServerParams>({
ConnectParam: {
Addr: "", Secret: "",
},
DNSLogLocalPort: 53,
DNSLogRemotePort: 0,
EnableDNSLogServer: false,
ExternalDomain: "",
FacadeRemotePort: 0,
LocalFacadeHost: "0.0.0.0",
LocalFacadePort: 4434,
Verify: false
});
const [token, _] = useState(randomString(40));
const [loading, setLoading] = useState(false);
const [logs, setLogs, getLogs] = useGetState<ReverseNotification[]>([]);
const [reverseToken, setReverseToken] = useState(randomString(20));
useEffect(() => {
const messages: ReverseNotification[] = [];
ipcRenderer.on(`${token}-data`, (_, data: ExecResult) => {
if (!data.IsMessage) {
return
}
try {
const message = ExtractExecResultMessage(data) as ExecResultLog;
if (message.level !== "facades-msg") {
info(JSON.stringify(message))
return
}
const obj = JSON.parse(message.data) as ReverseNotification;
obj.timestamp = message.timestamp;
messages.unshift(obj)
if (messages.length > 100) {
messages.pop()
}
} catch (e) {
}
})
ipcRenderer.on(`${token}-error`, (e: any, data: any) => {
if (data) {
failed(`error: ${JSON.stringify(data)}`)
}
})
ipcRenderer.on(`${token}-end`, () => {
setLoading(false)
})
const id = setInterval(() => {
if (getLogs().length !== messages.length || getLogs().length === 0) {
setLogs([...messages])
return
}
if (messages.length <= 0) {
return
}
if (messages.length > 0) {
if (messages[0].uuid !== getLogs()[0].uuid) {
setLogs([...messages])
}
}
}, 500)
return () => {
clearInterval(id)
ipcRenderer.invoke("cancel-StartFacades", token)
ipcRenderer.removeAllListeners(`${token}-end`);
ipcRenderer.removeAllListeners(`${token}-error`);
ipcRenderer.removeAllListeners(`${token}-data`);
}
}, [token])
const connectBridge = useMemoizedFn(() => {
setBridgeLoading(true)
ipcRenderer.invoke("GetTunnelServerExternalIP", {
Addr: bridgeAddr, Secret: bridgeSecret,
}).then((data: { IP: string }) => {
saveValue(BRIDGE_ADDR, bridgeAddr)
saveValue(BRIDGE_SECRET, bridgeSecret)
setBridgeIP(data.IP)
}).finally(() => {
setBridgeLoading(false)
})
});
// 设置 Bridge
useEffect(() => {
if (!bridgeAddr) {
getValue(BRIDGE_ADDR).then((data: string) => {
if (!!data) {
setBridgeAddr(`${data}`)
}
})
}
if (!bridgeSecret) {
getValue(BRIDGE_SECRET).then((data: string) => {
if (!!data) {
setBridgeSecret(`${data}`)
}
})
}
}, [])
useEffect(() => {
setBridgeLoading(true)
setTimeout(() => {
connectBridge()
}, 500)
}, [])
useEffect(() => {
if (!!bridgeIP) {
setBridge(false)
setParams({...params, ConnectParam: {Addr: bridgeAddr, Secret: bridgeSecret}})
}
}, [bridgeIP])
return <div>
<PageHeader
title={"反连服务器"}
subTitle={<Space>
{bridgeIP ? <Tag
onClose={() => {
setBridge(true)
setBridgeIP("")
}}
closable={true}
color={"green"}>公网 <Text strong={true} style={{color: "#229900"}} copyable={true}
>{bridgeIP}</Text></Tag> : <Form onSubmitCapture={e => e.preventDefault()}>
<SwitchItem size={"small"} label={"公网穿透服务"} value={bridge} setValue={setBridge}
formItemStyle={{marginBottom: 0}}/>
</Form>}
使用协议端口复用技术,同时在一个端口同时实现 HTTP / RMI / HTTPS 等协议的反连
</Space>}
extra={<>
<Space>
{loading && <Button
danger={true} type={"primary"}
onClick={() => {
ipcRenderer.invoke("cancel-StartFacades", token)
}}
>关闭反连</Button>}
</Space>
</>}
>
{bridge && <Card title={"公网配置"} size={"small"}>
<AutoSpin spinning={bridgeLoading}>
<Space direction={"vertical"}>
<Alert type={"success"} message={<Space>
<div>
在自己的服务器安装 yak 核心引擎,执行 <Text code={true} copyable={true}>yak bridge --secret
[your-pass]</Text> 启动
Yak Bridge 公网服务 <Divider type={"vertical"}/> <Text style={{color: "#999"}}>yak
version {`>=`} v1.0.11-sp9</Text>
</div>
</Space>}/>
<Form onSubmitCapture={e => {
e.preventDefault()
connectBridge()
}} layout={"inline"}>
<InputItem label={"公网 Bridge 地址"} value={bridgeAddr} setValue={setBridgeAddr}/>
<InputItem label={"密码"} type={"password"} value={bridgeSecret} setValue={setBridgeSecret}/>
<Form.Item colon={false} label={" "}>
<Button type="primary" htmlType="submit"> 连接公网服务器 </Button>
</Form.Item>
</Form>
</Space>
</AutoSpin>
</Card>}
{loading && <Alert
type={"info"}
message={<Space direction={"vertical"}>
<Space>
本地 RMI 反连 <CopyableField
text={`rmi://${bridgeIP && params.ConnectParam?.Addr ? bridgeIP : "127.0.0.1"}:${params.LocalFacadePort}/${reverseToken}`}/>
</Space>
<Space>
本地 HTTP 反连 <CopyableField
text={`http://${bridgeIP && params.ConnectParam?.Addr ? bridgeIP : "127.0.0.1"}:${params.LocalFacadePort}/${reverseToken}`}/>
</Space>
<Space>
本地 HTTPS 反连 <CopyableField
text={`https://${bridgeIP && params.ConnectParam?.Addr ? bridgeIP : "127.0.0.1"}:${params.LocalFacadePort}/${reverseToken}`}/>
</Space>
</Space>}>
</Alert>}
</PageHeader>
<Row>
<div style={{width: "100%"}}>
{loading ? <>
<ReverseNotificationTable loading={loading} logs={logs}/>
</> : <StartFacadeServerForm
params={params} setParams={setParams}
remoteMode={!!bridgeIP}
onSubmit={() => {
ipcRenderer.invoke("StartFacades", {
...params,
ConnectParam: (!!bridgeIP) ? params.ConnectParam : undefined
} as StartFacadeServerParams, token).then(() => {
info("开始启动反连服务器")
setLoading(true)
})
}}/>}
</div>
</Row>
</div>
}
Example #11
Source File: PortScanPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
PortScanPage: React.FC<PortScanPageProp> = (props) => {
const [loading, setLoading] = useState(false)
const [params, setParams, getParams] = useGetState<PortScanParams>({
Ports: defaultPorts,
Mode: "fingerprint",
Targets: props.sendTarget ? JSON.parse(props.sendTarget || "[]").join(",") : "",
Active: true,
Concurrent: 50,
FingerprintMode: "all",
Proto: ["tcp"],
SaveClosedPorts: false,
SaveToDB: true,
Proxy: [],
ProbeTimeout: 7,
ScriptNames: [],
ProbeMax: 3,
EnableCClassScan: false,
HostAlivePorts: "22,80,443",
})
const [token, setToken] = useState(randomString(40))
const xtermRef = useRef(null)
const [openPorts, setOpenPorts] = useState<YakitPort[]>([])
// const [closedPorts, setClosedPorts] = useState<YakitPort[]>([])
const [port, setPort] = useState<PortAsset>()
const [uploadLoading, setUploadLoading] = useState(false)
const [templatePort, setTemplatePort] = useState<string>()
const openPort = useRef<YakitPort[]>([])
// const closedPort = useRef<YakitPort[]>([])
const [infoState, {reset}] = useHoldingIPCRStream(
"scan-port",
"PortScan",
token,
() => {
},
() => {
},
(obj, content) => content.data.indexOf("isOpen") > -1 && content.data.indexOf("port") > -1
)
useEffect(() => {
setLoading(true)
ipcRenderer
.invoke("get-value", ScanPortTemplate)
.then((value: any) => {
if (value) {
setTemplatePort(value || "")
// setTimeout(() => {
// setParams({...getParams(), Ports: value || ""})
// }, 300)
}
})
.catch(() => {
})
.finally(() => {
setTimeout(() => setLoading(false), 100)
})
}, [])
useEffect(() => {
if (!xtermRef) {
return
}
ipcRenderer.on(`${token}-data`, async (e: any, data: ExecResult) => {
if (data.IsMessage) {
try {
let messageJsonRaw = Buffer.from(data.Message).toString("utf8")
let logInfo = ExtractExecResultMessageToYakitPort(JSON.parse(messageJsonRaw))
if (!logInfo) return
if (logInfo.isOpen) {
openPort.current.unshift(logInfo)
} else {
// closedPort.current.unshift(logInfo)
}
} catch (e) {
failed("解析端口扫描结果失败...")
}
}
writeExecResultXTerm(xtermRef, data)
})
ipcRenderer.on(`${token}-error`, (e: any, error: any) => {
failed(`[PortScan] error: ${error}`)
})
ipcRenderer.on(`${token}-end`, (e: any, data: any) => {
info("[PortScan] finished")
setLoading(false)
})
const syncPorts = () => {
if (openPort.current) setOpenPorts([...openPort.current])
// if (closedPort.current) setClosedPorts([...closedPort.current])
}
syncPorts()
let id = setInterval(syncPorts, 1000)
return () => {
clearInterval(id)
ipcRenderer.invoke("cancel-PortScan", token)
ipcRenderer.removeAllListeners(`${token}-data`)
ipcRenderer.removeAllListeners(`${token}-error`)
ipcRenderer.removeAllListeners(`${token}-end`)
}
}, [xtermRef])
return (
<div style={{width: "100%", height: "100%"}}>
<Tabs className='scan-port-tabs' tabBarStyle={{marginBottom: 5}}>
<Tabs.TabPane tab={"扫描端口操作台"} key={"scan"}>
<div className='scan-port-body'>
<div style={{width: 360, height: "100%"}}>
<SimplePluginList
pluginTypes={"port-scan,mitm"}
initialSelected={params.ScriptNames}
onSelected={l => {
setParams({...params, ScriptNames: [...l]})
}}
/>
</div>
<div className='right-container'>
<div style={{width: "100%"}}>
<Form
labelAlign='right'
labelCol={{span: 5}}
onSubmitCapture={(e) => {
e.preventDefault()
if (!token) {
failed("No Token Assigned")
return
}
if (!params.Targets && !params.TargetsFile) {
failed("需要设置扫描目标")
return
}
setLoading(true)
openPort.current = []
// closedPort.current = []
reset()
xtermClear(xtermRef)
ipcRenderer.invoke("PortScan", params, token)
}}
>
<Spin spinning={uploadLoading}>
<ContentUploadInput
type="textarea"
beforeUpload={(f) => {
if (f.type !== "text/plain") {
failed(`${f.name}非txt文件,请上传txt格式文件!`)
return false
}
setUploadLoading(true)
ipcRenderer.invoke("fetch-file-content", (f as any).path).then((res) => {
setParams({...params, Targets: res})
setTimeout(() => setUploadLoading(false), 100)
})
return false
}}
item={{
style: {textAlign: "left"},
label: "扫描目标",
}}
textarea={{
isBubbing: true,
setValue: (Targets) => setParams({...params, Targets}),
value: params.Targets,
rows: 1,
placeholder: "域名/主机/IP/IP段均可,逗号分隔或按行分割"
}}
suffixNode={
loading ? (
<Button
className="form-submit-style"
type='primary'
danger
onClick={(e) => ipcRenderer.invoke("cancel-PortScan", token)}
>
停止扫描
</Button>
) : (
<Button
className="form-submit-style"
type='primary'
htmlType='submit'
>
开始扫描
</Button>
)
}
/>
</Spin>
<Form.Item label='预设端口' colon={false} className='form-item-margin'>
<Checkbox.Group
onChange={(value) => {
let res: string = (value || [])
.map((i) => {
if (i === "template") return templatePort
// @ts-ignore
return PresetPorts[i] || ""
})
.join(",")
if (!!res) {
setParams({...params, Ports: res})
}
}}
>
<Checkbox value={"top100"}>常见100端口</Checkbox>
<Checkbox value={"topweb"}>常见 Web 端口</Checkbox>
<Checkbox value={"top1000+"}>常见一两千</Checkbox>
<Checkbox value={"topdb"}>常见数据库与 MQ</Checkbox>
<Checkbox value={"topudp"}>常见 UDP 端口</Checkbox>
{templatePort && <Checkbox value={"template"}>默认模板</Checkbox>}
</Checkbox.Group>
</Form.Item>
<Form.Item label='扫描端口' colon={false} className='form-item-margin'>
<Input.TextArea
style={{width: "75%"}}
rows={2}
value={params.Ports}
onChange={(e) => setParams({...params, Ports: e.target.value})}
/>
<Space size={"small"} style={{marginBottom: 4}}>
<Tooltip title={"保存为模版"}>
<a className="link-button-bfc"
onClick={() => {
if (!params.Ports) {
failed("请输入端口后再保存")
return
}
ipcRenderer.invoke("set-value", ScanPortTemplate, params.Ports).then(() => {
setTemplatePort(params.Ports)
success("保存成功")
})
}}>保存</a>
</Tooltip>
<Tooltip title={"重置为默认扫描端口"}>
<a href={"#"} onClick={() => {
setParams({...params, Ports: defaultPorts})
}}><ReloadOutlined/></a>
</Tooltip>
</Space>
</Form.Item>
<Form.Item label=' ' colon={false} className='form-item-margin'>
<Space>
<Tag>扫描模式:{ScanKind[params.Mode]}</Tag>
<Tag>并发:{params.Concurrent}</Tag>
<Checkbox onClick={e => {
setParams({
...params,
SkippedHostAliveScan: !params.SkippedHostAliveScan
})
}} checked={params.SkippedHostAliveScan}>
跳过主机存活检测
</Checkbox>
<Button
type='link'
size='small'
onClick={() => {
showModal({
title: "设置高级参数",
width: "50%",
content: (
<>
<ScanPortForm
defaultParams={params}
setParams={setParams}
/>
</>
)
})
}}
>
更多参数
</Button>
</Space>
</Form.Item>
</Form>
</div>
<Divider style={{margin: "5px 0"}}/>
<div style={{flex: 1, overflow: "hidden"}}>
<Tabs className='scan-port-tabs' tabBarStyle={{marginBottom: 5}}>
<Tabs.TabPane tab={"扫描端口列表"} key={"scanPort"} forceRender>
<div style={{width: "100%", height: "100%", overflow: "hidden auto"}}>
<div style={{textAlign: "right", marginBottom: 8}}>
{loading ? (
<Tag color={"green"}>正在执行...</Tag>
) : (
<Tag>闲置中...</Tag>
)}
</div>
<div style={{width: "100%", height: 178, overflow: "hidden"}}>
<CVXterm
ref={xtermRef}
options={{
convertEol: true,
disableStdin: true
}}
/>
</div>
<Row style={{marginTop: 6}} gutter={6}>
<Col span={24}>
<OpenPortTableViewer data={openPorts}/>
</Col>
{/*<Col span={8}>*/}
{/* <ClosedPortTableViewer data={closedPorts}/>*/}
{/*</Col>*/}
</Row>
</div>
</Tabs.TabPane>
<Tabs.TabPane tab={"插件日志"} key={"pluginPort"} forceRender>
<div style={{width: "100%", height: "100%", overflow: "hidden auto"}}>
<PluginResultUI
loading={loading}
progress={infoState.processState}
results={infoState.messageState}
featureType={infoState.featureTypeState}
feature={infoState.featureMessageState}
statusCards={infoState.statusState}
/>
</div>
</Tabs.TabPane>
</Tabs>
</div>
</div>
</div>
</Tabs.TabPane>
<Tabs.TabPane tab={"端口资产管理"} key={"port"}>
<div style={{height: "100%", overflowY: "auto", padding: "0 6px"}}>
<PortAssetTable
onClicked={(i) => {
setPort(i)
}}
/>
</div>
</Tabs.TabPane>
</Tabs>
</div>
)
}
Example #12
Source File: MITMPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
MITMPage: React.FC<MITMPageProp> = (props) => {
const [status, setStatus] = useState<"idle" | "hijacked" | "hijacking">("idle");
const [error, setError] = useState("");
const [host, setHost] = useState("127.0.0.1");
const [port, setPort] = useState(8083);
const [downstreamProxy, setDownstreamProxy] = useState<string>();
const [loading, setLoading] = useState(false);
const [caCerts, setCaCerts] = useState<CaCertData>({
CaCerts: new Buffer(""), LocalFile: "",
});
const [enableInitialPlugin, setEnableInitialPlugin] = useState(false);
// 存储修改前和修改后的包!
const [currentPacketInfo, setCurrentPacketInfo] = useState<{
currentPacket: Uint8Array,
currentPacketId: number,
isHttp: boolean
}>({currentPacketId: 0, currentPacket: new Buffer([]), isHttp: true});
const {currentPacket, currentPacketId, isHttp} = currentPacketInfo;
const clearCurrentPacket = () => {
setCurrentPacketInfo({currentPacketId: 0, currentPacket: new Buffer([]), isHttp: true})
}
const [modifiedPacket, setModifiedPacket] = useState<Uint8Array>(new Buffer([]));
// 自动转发 与 劫持响应的自动设置
const [autoForward, setAutoForward, getAutoForward] = useGetState<"manual" | "log" | "passive">("log");
const isManual = autoForward === "manual";
const [hijackAllResponse, setHijackAllResponse] = useState(false); // 劫持所有请求
const [allowHijackCurrentResponse, setAllowHijackCurrentResponse] = useState(false); // 仅劫持一个请求
const [initialed, setInitialed] = useState(false);
const [forResponse, setForResponse] = useState(false);
const [haveSideCar, setHaveSideCar] = useState(true);
const [urlInfo, setUrlInfo] = useState("监听中...")
const [ipInfo, setIpInfo] = useState("")
// 设置初始化启动的插件
const [defaultPlugins, setDefaultPlugins] = useState<string[]>([]);
// yakit log message
const [logs, setLogs] = useState<ExecResultLog[]>([]);
const latestLogs = useLatest<ExecResultLog[]>(logs);
const [_, setLatestStatusHash, getLatestStatusHash] = useGetState("");
const [statusCards, setStatusCards] = useState<StatusCardProps[]>([])
// filter 过滤器
const [mitmFilter, setMITMFilter] = useState<MITMFilterSchema>();
// 内容替代模块
const [replacers, setReplacers] = useState<MITMContentReplacerRule[]>([]);
// mouse
const mouseState = useMouse();
// 操作系统类型
const [system, setSystem] = useState<string>()
useEffect(() => {
ipcRenderer.invoke('fetch-system-name').then((res) => setSystem(res))
}, [])
useEffect(() => {
// 设置 MITM 初始启动插件选项
getValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN).then(a => {
setEnableInitialPlugin(!!a)
})
}, [])
// 用于接受后端传回的信息
useEffect(() => {
setInitialed(false)
// 用于前端恢复状态
ipcRenderer.invoke("mitm-have-current-stream").then(data => {
const {haveStream, host, port} = data;
if (haveStream) {
setStatus("hijacking")
setHost(host);
setPort(port);
}
}).finally(() => {
recover()
setTimeout(() => setInitialed(true), 500)
})
// 用于启动 MITM 开始之后,接受开始成功之后的第一个消息,如果收到,则认为说 MITM 启动成功了
ipcRenderer.on("client-mitm-start-success", () => {
setStatus("hijacking")
setTimeout(() => {
setLoading(false)
}, 300)
})
// 用于 MITM 的 Message (YakitLog)
const messages: ExecResultLog[] = [];
const statusMap = new Map<string, StatusCardProps>();
let lastStatusHash = '';
ipcRenderer.on("client-mitm-message", (e, data: ExecResult) => {
let msg = ExtractExecResultMessage(data);
if (msg !== undefined) {
// logHandler.logs.push(msg as ExecResultLog)
// if (logHandler.logs.length > 25) {
// logHandler.logs.shift()
// }
const currentLog = msg as ExecResultLog;
if (currentLog.level === "feature-status-card-data") {
lastStatusHash = `${currentLog.timestamp}-${currentLog.data}`
try {
// 解析 Object
const obj = JSON.parse(currentLog.data)
const {id, data} = obj;
if (!data) {
statusMap.delete(`${id}`)
} else {
statusMap.set(`${id}`, {Data: data, Id: id, Timestamp: currentLog.timestamp})
}
} catch (e) {
}
return
}
messages.push(currentLog)
if (messages.length > 25) {
messages.shift()
}
}
})
// let currentFlow: HTTPFlow[] = []
ipcRenderer.on("client-mitm-history-update", (e: any, data: any) => {
// currentFlow.push(data.historyHTTPFlow as HTTPFlow)
//
// if (currentFlow.length > 30) {
// currentFlow = [...currentFlow.slice(0, 30)]
// }
// setFlows([...currentFlow])
})
ipcRenderer.on("client-mitm-error", (e, msg) => {
if (!msg) {
info("MITM 劫持服务器已关闭")
} else {
failed("MITM 劫持服务器异常或被关闭")
Modal.error({
mask: true, title: "启动 MITM 服务器 ERROR!",
content: <>{msg}</>
})
}
ipcRenderer.invoke("mitm-stop-call")
setError(`${msg}`)
setStatus("idle")
setTimeout(() => {
setLoading(false)
}, 300)
});
ipcRenderer.on("client-mitm-filter", (e, msg) => {
ipcRenderer
.invoke("get-value", DefaultMitmFilter)
.then((res: any) => {
if (res) {
const filter = {
includeSuffix: res.includeSuffix,
excludeMethod: res.excludeMethod,
excludeSuffix: res.excludeSuffix,
includeHostname: res.includeHostname,
excludeHostname: res.excludeHostname,
excludeContentTypes: res.excludeContentTypes,
}
setMITMFilter(filter)
ipcRenderer.invoke("mitm-filter", {
updateFilter: true, ...filter
})
} else {
setMITMFilter({
includeSuffix: msg.includeSuffix,
excludeMethod: msg.excludeMethod,
excludeSuffix: msg.excludeSuffix,
includeHostname: msg.includeHostname,
excludeHostname: msg.excludeHostname,
excludeContentTypes: msg.excludeContentTypes,
})
}
})
})
const updateLogs = () => {
if (latestLogs.current.length !== messages.length) {
setLogs([...messages])
return
}
if (latestLogs.current.length > 0 && messages.length > 0) {
if (latestLogs.current[0].data !== messages[0].data) {
setLogs([...messages])
return
}
}
if (getLatestStatusHash() !== lastStatusHash) {
setLatestStatusHash(lastStatusHash)
const tmpCurrent: StatusCardProps[] = [];
statusMap.forEach((value, key) => {
tmpCurrent.push(value)
})
setStatusCards(tmpCurrent.sort((a, b) => a.Id.localeCompare(b.Id)))
}
}
updateLogs()
let id = setInterval(() => {
updateLogs()
}, 1000)
return () => {
clearInterval(id);
ipcRenderer.removeAllListeners("client-mitm-error")
// ipcRenderer.invoke("mitm-close-stream")
}
}, [])
useEffect(() => {
if (hijackAllResponse && currentPacketId > 0) {
allowHijackedResponseByRequest(currentPacketId)
}
}, [hijackAllResponse, currentPacketId])
useEffect(() => {
ipcRenderer.on("client-mitm-hijacked", forwardHandler);
return () => {
ipcRenderer.removeAllListeners("client-mitm-hijacked")
}
}, [autoForward])
useEffect(() => {
ipcRenderer.invoke("mitm-auto-forward", !isManual).finally(() => {
console.info(`设置服务端自动转发:${!isManual}`)
})
}, [autoForward])
useEffect(() => {
ipcRenderer.on("client-mitm-content-replacer-update", (e, data: MITMResponse) => {
setReplacers(data?.replacers || [])
return
});
return () => {
ipcRenderer.removeAllListeners("client-mitm-content-replacer-update")
}
}, [])
useEffect(() => {
if (currentPacketId <= 0 && status === "hijacked") {
recover()
const id = setInterval(() => {
recover()
}, 500)
return () => {
clearInterval(id)
}
}
}, [currentPacketId])
useEffect(() => {
ipcRenderer.invoke("DownloadMITMCert", {}).then((data: CaCertData) => {
setCaCerts(data)
})
}, [])
const addr = `http://${host}:${port}`;
// 自动转发劫持,进行的操作
const forwardHandler = useMemoizedFn((e: any, msg: MITMResponse) => {
setMITMFilter({
includeSuffix: msg.includeSuffix,
excludeMethod: msg.excludeMethod,
excludeSuffix: msg.excludeSuffix,
includeHostname: msg.includeHostname,
excludeHostname: msg.excludeHostname,
excludeContentTypes: msg.excludeContentTypes,
})
// passive 模式是 mitm 插件模式
// 在这个模式下,应该直接转发,不应该操作数据包
// if (passiveMode) {
// if (msg.forResponse) {
// forwardResponse(msg.responseId || 0)
// } else {
// forwardRequest(msg.id || 0)
// }
// return
// }
if (msg.forResponse) {
if (!msg.response || !msg.responseId) {
failed("BUG: MITM 错误,未能获取到正确的 Response 或 Response ID")
return
}
if (!isManual) {
forwardResponse(msg.responseId || 0)
if (!!currentPacket) {
clearCurrentPacket()
}
} else {
setForResponse(true)
setStatus("hijacked")
setCurrentPacketInfo({
currentPacket: msg.response,
currentPacketId: msg.responseId,
isHttp: msg.isHttps
})
// setCurrentPacket(new Buffer(msg.response).toString("utf8"))
// setCurrentPacketId(msg.responseId || 0);
}
} else {
if (msg.request) {
if (!isManual) {
forwardRequest(msg.id)
if (!!currentPacket) {
clearCurrentPacket()
}
// setCurrentPacket(String.fromCharCode.apply(null, msg.request))
} else {
setStatus("hijacked")
setForResponse(false)
// setCurrentPacket(msg.request)
// setCurrentPacketId(msg.id)
setCurrentPacketInfo({currentPacket: msg.request, currentPacketId: msg.id, isHttp: msg.isHttps})
setUrlInfo(msg.url)
ipcRenderer.invoke("fetch-url-ip", msg.url.split('://')[1].split('/')[0]).then((res) => {
setIpInfo(res)
})
}
}
}
})
// 这个 Forward 主要用来转发修改后的内容,同时可以转发请求和响应
const forward = useMemoizedFn(() => {
// ID 不存在
if (!currentPacketId) {
return
}
setLoading(true);
setStatus("hijacking");
setAllowHijackCurrentResponse(false)
setForResponse(false)
if (forResponse) {
ipcRenderer.invoke("mitm-forward-modified-response", modifiedPacket, currentPacketId).finally(() => {
clearCurrentPacket()
setTimeout(() => setLoading(false))
})
} else {
ipcRenderer.invoke("mitm-forward-modified-request", modifiedPacket, currentPacketId).finally(() => {
clearCurrentPacket()
setTimeout(() => setLoading(false))
})
}
})
const recover = useMemoizedFn(() => {
ipcRenderer.invoke("mitm-recover").then(() => {
// success("恢复 MITM 会话成功")
})
})
const start = useMemoizedFn(() => {
setLoading(true)
setError("")
ipcRenderer.invoke("mitm-start-call", host, port, downstreamProxy).catch((e: any) => {
notification["error"]({message: `启动中间人劫持失败:${e}`})
})
})
const stop = useMemoizedFn(() => {
setLoading(true)
ipcRenderer.invoke("mitm-stop-call").then(() => {
setStatus("idle")
}).catch((e: any) => {
notification["error"]({message: `停止中间人劫持失败:${e}`})
}).finally(() => setTimeout(() => {
setLoading(false)
}, 300))
})
const hijacking = useMemoizedFn(() => {
// setCurrentPacket(new Buffer([]));
clearCurrentPacket()
setLoading(true);
setStatus("hijacking");
})
function getCurrentId() {
return currentPacketId
}
const downloadCert = useMemoizedFn(() => {
return <Tooltip title={'请先下载 SSL/TLS 证书'}>
<Button
type={"link"}
style={{padding: '4px 6px'}}
onClick={() => {
const text = `wget -e use_proxy=yes -e http_proxy=${addr} http://download-mitm-cert.yaklang.io -O yakit-mitm-cert.pem`
showModal({
title: "下载 SSL/TLS 证书以劫持 HTTPS",
width: "50%",
content: <Space direction={"vertical"} style={{width: "100%"}}>
<AutoCard
title={"证书配置"}
extra={<Button
type={"link"}
onClick={() => {
saveABSFileToOpen("yakit证书.crt.pem", caCerts.CaCerts)
// openABSFileLocated(caCerts.LocalFile)
}}
>
下载到本地并打开
</Button>} size={"small"} bodyStyle={{padding: 0}}>
<div style={{height: 360}}>
<YakEditor bytes={true}
valueBytes={caCerts.CaCerts}
/>
</div>
</AutoCard>
<Alert message={<Space>
在设置代理后访问:<CopyableField text={"http://download-mitm-cert.yaklang.io"}/> 可自动下载证书
</Space>}/>
</Space>
})
}}
>HTTPS 证书配置</Button>
</Tooltip>
})
const contentReplacer = useMemoizedFn(() => {
return <Button
type={"link"} style={{padding: `4px 6px`}}
onClick={() => {
let m = showDrawer({
placement: "top", height: "50%",
content: (
<MITMContentReplacer
rules={replacers}
onSaved={rules => {
setReplacers(rules)
m.destroy()
}}/>
),
maskClosable: false,
})
}}
>
匹配/标记/替换
</Button>
})
const setFilter = useMemoizedFn(() => {
return <Button type={"link"} style={{padding: '4px 6px'}}
onClick={() => {
let m = showDrawer({
placement: "top", height: "50%",
content: <>
<MITMFilters
filter={mitmFilter}
onFinished={(filter) => {
setMITMFilter({...filter})
m.destroy()
}}/>
</>
});
}}
>过滤器</Button>
})
const handleAutoForward = useMemoizedFn((e: "manual" | "log" | "passive") => {
if (!isManual) {
info("切换为劫持自动放行模式(仅记录)")
setHijackAllResponse(false)
} else {
info("切换为手动放行模式(可修改劫持)")
}
setAutoForward(e)
if (currentPacket && currentPacketId) {
forward()
}
})
const execFuzzer = useMemoizedFn((value: string) => {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {isHttps: currentPacketInfo.isHttp, request: value}
})
})
const execPlugin = useMemoizedFn((value: string) => {
ipcRenderer.invoke("send-to-packet-hack", {
request: currentPacketInfo.currentPacket,
ishttps: currentPacketInfo.isHttp
})
})
const shiftAutoForwardHotkey = useHotkeys('ctrl+t', () => {
handleAutoForward(isManual ? "manual" : "log")
}, [autoForward])
if (!initialed) {
return <div style={{textAlign: "center", paddingTop: 120}}>
<Spin spinning={true} tip={"正在初始化 MITM"}/>
</div>
}
return <div style={{height: "100%", width: "100%"}}>
{(() => {
switch (status) {
case "idle":
return <Spin spinning={loading}>
<Form
style={{marginTop: 40}}
onSubmitCapture={e => {
e.preventDefault()
start()
if (enableInitialPlugin) {
enableMITMPluginMode(defaultPlugins).then(() => {
info("被动扫描插件模式已启动")
})
}
}}
layout={"horizontal"} labelCol={{span: 7}}
wrapperCol={{span: 13}}
>
<Item label={"劫持代理监听主机"}>
<Input value={host} onChange={e => setHost(e.target.value)}/>
</Item>
<Item label={"劫持代理监听端口"}>
<InputNumber value={port} onChange={e => setPort(e)}/>
</Item>
{/*<SwitchItem label={"启动 MITM 插件"} size={"small"} setValue={e => {*/}
{/* setEnableInitialPlugin(e)*/}
{/* if (e) {*/}
{/* saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "true")*/}
{/* } else {*/}
{/* saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "")*/}
{/* }*/}
{/*}} value={enableInitialPlugin}/>*/}
<Item label={"选择插件"} colon={true}>
<div style={{height: 200, maxWidth: 420}}>
<SimplePluginList
disabled={!enableInitialPlugin}
bordered={true}
initialSelected={defaultPlugins}
onSelected={(list: string[]) => {
setDefaultPlugins(list)
}} pluginTypes={"mitm,port-scan"}
verbose={<div>MITM 与 端口扫描插件</div>}/>
</div>
</Item>
<Item label={"下游代理"} help={"为经过该 MITM 代理的请求再设置一个代理,通常用于访问中国大陆无法访问的网站或访问特殊网络/内网,也可用于接入被动扫描"}>
<Input value={downstreamProxy} onChange={e => setDownstreamProxy(e.target.value)}/>
</Item>
<Item label={"内容规则"} help={"使用规则进行匹配、替换、标记、染色,同时配置生效位置"}>
<Space>
<Button
onClick={() => {
let m = showDrawer({
placement: "top", height: "50%",
content: (
<MITMContentReplacerViewer/>
),
maskClosable: false,
})
}}
>已有规则</Button>
<Button type={"link"} onClick={() => {
const m = showModal({
title: "从 JSON 中导入",
width: "60%",
content: (
<>
<MITMContentReplacerImport onClosed={() => {
m.destroy()
}}/>
</>
)
})
}}>从 JSON 导入</Button>
<Button type={"link"} onClick={() => {
showModal({
title: "导出配置 JSON",
width: "50%",
content: (
<>
<MITMContentReplacerExport/>
</>
)
})
}}>导出为 JSON</Button>
</Space>
</Item>
<Item label={" "} colon={false}>
<Space>
<Button type={"primary"} htmlType={"submit"}>
劫持启动
</Button>
<Divider type={"vertical"}/>
<Checkbox
checked={enableInitialPlugin}
onChange={node => {
const e = node.target.checked;
setEnableInitialPlugin(e)
if (e) {
saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "true")
} else {
saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "")
}
}}
>
插件自动加载
</Checkbox>
</Space>
</Item>
</Form>
</Spin>
case "hijacking":
case "hijacked":
return <div id={"mitm-hijacking-container"} ref={shiftAutoForwardHotkey as Ref<any>} tabIndex={-1}
style={{marginLeft: 12, marginRight: 12, height: "100%"}}>
<Row gutter={14} style={{height: "100%"}}>
<Col span={haveSideCar ? 24 : 24}
style={{display: "flex", flexDirection: "column", height: "100%"}}>
<PageHeader
className="mitm-header-title"
title={'劫持 HTTP Request'} subTitle={`http://${host}:${port}`}
style={{marginRight: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 8}}
extra={
<Space>
<ChromeLauncherButton host={host} port={port}/>
{contentReplacer()}
{setFilter()}
{downloadCert()}
<Button danger={true} type={"link"}
onClick={() => {
stop()
setUrlInfo("监听中...")
setIpInfo("")
}} icon={<PoweroffOutlined/>}
/>
</Space>}>
<Row>
<Col span={12}>
<div style={{width: "100%", textAlign: "left"}}>
<Space>
<Button
type={"primary"}
disabled={status === "hijacking"}
onClick={() => {
forward()
}}>提交数据</Button>
<Button
disabled={status === "hijacking"}
danger={true}
onClick={() => {
hijacking()
if (forResponse) {
dropResponse(currentPacketId).finally(() => {
setTimeout(() => {
setLoading(false)
}, 300)
})
} else {
dropRequest(currentPacketId).finally(() => {
setTimeout(() => setLoading(false), 300)
})
}
setUrlInfo("监听中...")
setIpInfo("")
}}>丢弃请求</Button>
{
(!forResponse && !!currentPacket) && // 劫持到的请求有内容
status === "hijacked" && // 劫持到的状态是 hijacked
!hijackAllResponse && // 如果已经设置了劫持所有请求,就不展示了
<Button
disabled={allowHijackCurrentResponse}
type={allowHijackCurrentResponse ? "primary" : "default"}
onClick={() => {
if (!allowHijackCurrentResponse) {
allowHijackedResponseByRequest(currentPacketId)
setAllowHijackCurrentResponse(true)
} else {
setAllowHijackCurrentResponse(false)
}
}}>
劫持响应 {
allowHijackCurrentResponse &&
<CheckOutlined/>
}
</Button>}
</Space>
</div>
</Col>
<Col span={12}>
<div style={{width: "100%", textAlign: "right"}}>
<Space>
{isManual && <div>
<span style={{marginRight: 4}}>劫持响应:</span>
<Checkbox checked={hijackAllResponse} onClick={e => {
if (!hijackAllResponse) {
info("劫持所有响应内容")
} else {
info("仅劫持请求")
}
setHijackAllResponse(!hijackAllResponse)
}}/>
</div>}
<SelectOne
data={[
{text: "手动劫持", value: "manual"},
{text: "自动放行", value: "log"},
{text: "被动日志", value: "passive"},
]}
value={autoForward}
formItemStyle={{marginBottom: 0}}
setValue={(e) => {
ipcRenderer.invoke("mitm-filter", {updateFilter: true, ...mitmFilter})
handleAutoForward(e)
}}
/>
</Space>
</div>
</Col>
</Row>
<Row>
<Col span={12}>
<div style={{
width: "100%", textAlign: "left", height: '100%',
display: 'flex'
}}>
{!isManual &&
<Text style={{alignSelf: 'center'}}>
{`目标:自动放行中...`}</Text>}
{autoForward === "manual" &&
<>
<Text title={urlInfo} ellipsis={true} style={{
alignSelf: 'center',
maxWidth: 300
}}>{status === 'hijacking' ? '目标:监听中...' : `目标:${urlInfo}`}</Text>
{ipInfo && status !== 'hijacking' &&
<Tag
color='green'
title={ipInfo}
style={{
marginLeft: 5,
alignSelf: "center",
maxWidth: 140,
cursor: "pointer"
}}
>
{`${ipInfo}`}
<CopyToClipboard
text={`${ipInfo}`}
onCopy={(text, ok) => {
if (ok) success("已复制到粘贴板")
}}
>
<CopyOutlined style={{marginLeft: 5}}/>
</CopyToClipboard>
</Tag>
}
</>
}
</div>
</Col>
<Col span={12}>
<div style={{width: "100%", textAlign: "right"}}>
<Button
type={"link"} onClick={() => recover()}
icon={<ReloadOutlined/>}
>恢复请求</Button>
</div>
</Col>
</Row>
</PageHeader>
<div style={{flex: 1, overflowY: 'hidden'}}>
{/*<Spin wrapperClassName={"mitm-loading-spin"} spinning={status === "hijacking"}>*/}
<div style={{height: "100%"}}>
<ResizeBox
isVer={false}
firstNode={(
<MITMPluginList
proxy={`http://${host}:${port}`}
downloadCertNode={downloadCert}
setFilterNode={setFilter}
onExit={() => {
stop()
}}
onSubmitScriptContent={e => {
ipcRenderer.invoke("mitm-exec-script-content", e)
}}
onSubmitYakScriptId={(id: number, params: YakExecutorParam[]) => {
info(`加载 MITM 插件[${id}]`)
ipcRenderer.invoke("mitm-exec-script-by-id", id, params)
}}
/>
// <MITMPluginOperator />
)}
firstMinSize={"330px"}
secondMinSize={"340px"}
firstRatio={"330px"}
secondNode={(
<AutoCard
style={{margin: 0, padding: 0}}
bodyStyle={{margin: 0, padding: 0, overflowY: "hidden"}}
>
{autoForward === "log" && (
<MITMPluginCard
onSubmitScriptContent={(e) => {
ipcRenderer.invoke("mitm-exec-script-content", e)
}}
onSubmitYakScriptId={(
id: number,
params: YakExecutorParam[]
) => {
info(`加载 MITM 插件[${id}]`)
ipcRenderer.invoke("mitm-exec-script-by-id", id, params)
}}
/>
)}
{autoForward === "manual" && (
<HTTPPacketEditor
originValue={currentPacket}
noHeader={true}
bordered={false}
onChange={setModifiedPacket}
noPacketModifier={true}
readOnly={status === "hijacking"}
refreshTrigger={
(forResponse ? `rsp` : `req`) + `${currentPacketId}`
}
actions={[
// {
// id: "send-to-scan-packet", label: "发送到数据包扫描器",
// run: e => {
// // console.info(mouseState)
// scanPacket(mouseState, false, "GET / HTTP/1.1\r\nHost: www.baidu.com", "")
// }, contextMenuGroupId: "Scanners",
// },
...(forResponse
? [
{
id: "trigger-auto-hijacked",
label: "切换为自动劫持模式",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_T
],
run: () => {
handleAutoForward(getAutoForward() === "manual" ? "log" : "manual")
},
contextMenuGroupId: "Actions"
},
{
id: "forward-response",
label: "放行该 HTTP Response",
run: function () {
forward()
// hijacking()
// forwardResponse(getCurrentId()).finally(() => {
// setTimeout(() => setLoading(false), 300)
// })
},
contextMenuGroupId: "Actions"
},
{
id: "drop-response",
label: "丢弃该 HTTP Response",
run: function () {
hijacking()
dropResponse(getCurrentId()).finally(
() => {
setTimeout(
() => setLoading(false),
300
)
}
)
},
contextMenuGroupId: "Actions"
}
]
: [
{
id: "trigger-auto-hijacked",
label: "切换为自动劫持模式",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_T
],
run: () => {
handleAutoForward(getAutoForward() === "manual" ? "log" : "manual")
},
contextMenuGroupId: "Actions"
},
{
id: "send-to-fuzzer",
label: "发送到 Web Fuzzer",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_R
],
run: function (StandaloneEditor: any) {
execFuzzer(StandaloneEditor.getModel().getValue())
},
contextMenuGroupId: "Actions"
},
{
id: "send-to-plugin",
label: "发送到 数据包扫描",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_E
],
run: function (StandaloneEditor: any) {
if (!StandaloneEditor.getModel().getValue()) return
execPlugin(StandaloneEditor.getModel().getValue())
},
contextMenuGroupId: "Actions"
},
{
id: "forward-response",
label: "放行该 HTTP Request",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_F
],
run: function () {
forward()
// hijacking()
// forwardRequest(getCurrentId()).finally(() => {
// setTimeout(() => setLoading(false), 300)
// })
},
contextMenuGroupId: "Actions"
},
{
id: "drop-response",
label: "丢弃该 HTTP Request",
run: function () {
hijacking()
dropRequest(getCurrentId()).finally(
() => {
setTimeout(
() => setLoading(false),
300
)
}
)
},
contextMenuGroupId: "Actions"
},
{
id: "hijack-current-response",
label: "劫持该 Request 对应的响应",
run: function () {
allowHijackedResponseByRequest(
getCurrentId()
)
},
contextMenuGroupId: "Actions"
}
])
]}
/>
)}
{autoForward === "passive" && (
<MITMPluginLogViewer
messages={logs} status={statusCards}
/>
)}
</AutoCard>
)}
/>
</div>
{/*</Spin>*/}
</div>
</Col>
</Row>
</div>
default:
return <div/>
}
})()}
</div>
}
Example #13
Source File: YakBatchExecutors.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
BugTestExecutor: React.FC<YakBatchExecutorsProp> = (props) => {
const update = useUpdate()
const [listHeight, setListHeight] = useState<number>(500)
const [params, setParams] = useState<ExecBatchYakScriptParams>({
Concurrent: 5,
Keyword: props.keyword.split("-")[0],
Limit: 10000,
Target: props.sendTarget ? JSON.parse(props.sendTarget).join(",") : "",
DisableNucleiWorkflow: true,
ExcludedYakScript: [
"[fingerprinthub-web-fingerprints]: FingerprintHub Technology Fingerprint",
"[tech-detect]: Wappalyzer Technology Detection"
],
TotalTimeoutSeconds: 180,
Type: "nuclei"
})
const [totalLoading, setTotalLoading] = useState(true)
const [tasks, setTasks, getTasks] = useGetState<ExecBatchYakScriptTask[]>([])
const [filterTasks, setFilterTasks, getFilterTasks] = useGetState<ExecBatchYakScriptTask[]>([])
const [error, setError] = useState("")
const [token, setToken] = useState("")
const [executing, setExecuting] = useState(false)
const [checked, setChecked] = useState<boolean>(false)
const [uploadLoading, setUploadLoading] = useState(false)
const containerRef = useRef(null)
const wrapperRef = useRef(null)
const listRef = useRef(null)
const filterContainerRef = useRef(null)
const filterWrapperRef = useRef(null)
const [list] = useVirtualList(getTasks(), {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 50,
overscan: 5
})
const [filterList] = useVirtualList(getFilterTasks(), {
containerTarget: filterContainerRef,
wrapperTarget: filterWrapperRef,
itemHeight: 50,
overscan: 5
})
window.onresize = () => {
let timer: any = null
window.onresize = () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
if (!listRef || !listRef.current) return
const list = listRef.current as unknown as HTMLDivElement
setListHeight(list.clientHeight - 10)
}, 50)
}
}
useEffect(() => {
setTimeout(() => {
if (!listRef || !listRef.current) return
const list = listRef.current as unknown as HTMLDivElement
setListHeight(list.clientHeight - 10)
}, 600)
return () => {
window.onresize = null
}
}, [])
useEffect(() => {
let timer = setTimeout(() => {
update()
}, 100)
return () => {
clearTimeout(timer)
}
}, [listHeight])
useEffect(() => {
setTotalLoading(true)
setTimeout(() => setTotalLoading(false), 500)
const token = randomString(40)
setToken(token)
setTasks([])
setParams({...params, Keyword: props.keyword.split("-")[0]})
const tempTasks = new Map<string, ExecBatchYakScriptTask>()
const updateTasks = () => {
let items: ExecBatchYakScriptTask[] = []
tempTasks.forEach((v, k) => {
items.push(v)
})
setTasks(items.sort((a, b) => b.Id.localeCompare(a.Id)))
}
const dataChannel = `${token}-exec-batch-yak-script-data`
const errorChannel = `${token}-exec-batch-yak-script-error`
const endChannel = `${token}-exec-batch-yak-script-end`
let updateTableTick = setInterval(updateTasks, 1000)
ipcRenderer.on(dataChannel, async (e: any, data: ExecBatchYakScriptResult) => {
if (data.ProgressMessage) {
return
}
let element = tempTasks.get(data.Id)
if (element === undefined) {
tempTasks.set(data.Id, {
Id: data.Id,
PoC: data.PoC,
Results: [],
Status: data.Status,
progress: 0
})
// updateTasks()
return
} else {
element.Status = data.Status
if (!element.Ok) {
element.Ok = data.Ok || false
}
element.Reason = data.Reason
if (data.Result) {
element.Results.push({...data.Result})
}
// updateTasks()
return
}
})
ipcRenderer.on(errorChannel, (e: any, error: any) => {
setError(error)
})
ipcRenderer.on(endChannel, (e: any, data: any) => {
info("模块加载完成 / 执行完毕")
setExecuting(false)
updateTasks()
})
ipcRenderer.invoke(
"exec-batch-yak-script",
{...params, Keyword: props.keyword.split("-")[0], Target: ""},
token
)
setExecuting(true)
return () => {
clearInterval(updateTableTick)
ipcRenderer.invoke("cancel-exec-batch-yak-script", token)
ipcRenderer.removeAllListeners(dataChannel)
ipcRenderer.removeAllListeners(errorChannel)
ipcRenderer.removeAllListeners(endChannel)
}
}, [props.keyword])
// 转换task内的result数据
const convertTask = (task: ExecBatchYakScriptTask) => {
// @ts-ignore
const results: ExecResult[] = task.Results
const messages: ExecResultMessage[] = []
for (let item of results) {
if (!item.IsMessage) continue
try {
const raw = item.Message
const obj: ExecResultMessage = JSON.parse(Buffer.from(raw).toString("utf8"))
messages.push(obj)
} catch (e) {
console.error(e)
}
}
return messages
}
useEffect(() => {
if (checked) {
const filters: ExecBatchYakScriptTask[] = getTasks()
.filter((item) => item.Results.length !== 0)
.filter(
(item) =>
(
convertTask(item)
.filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[]
).filter((i) => ["json", "success"].includes((i?.level || "").toLowerCase())).length > 0
)
setFilterTasks(filters)
} else {
setFilterTasks([])
}
}, [checked])
useEffect(() => {
if (tasks) {
const filters: ExecBatchYakScriptTask[] = getTasks()
.filter((item) => item.Results.length !== 0)
.filter(
(item) =>
(
convertTask(item)
.filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[]
).filter((i) => ["json", "success"].includes((i?.level || "").toLowerCase())).length > 0
)
if (JSON.stringify(filterTasks) !== JSON.stringify(filters)) setFilterTasks(filters)
}
}, [tasks])
if (totalLoading) {
return (
<div style={{textAlign: "center", width: "100%", marginTop: 100}}>
<Spin>正在加载专用漏洞库</Spin>
</div>
)
}
return (
<div className='bug-test-container'>
<Row>
<Col span={3}></Col>
<Col span={18}>
<Form
style={{textAlign: "center"}}
onSubmitCapture={(e) => {
e.preventDefault()
if (tasks.length === 0) {
Modal.error({title: "模块还未加载,请点击右上角配置进行YAML POC更新"})
return
}
if (!params.Target) {
Modal.error({title: "检测目标不能为空"})
return
}
if (!params.Keyword) {
Modal.error({title: "无 PoC 关键字选择"})
return
}
if (!token) {
Modal.error({title: "BUG:无 Token 生成,请重新打开该页"})
}
ipcRenderer.invoke("exec-batch-yak-script", params, token)
setExecuting(true)
setChecked(false)
}}
>
<Space style={{width: "80%"}} direction={"vertical"}>
<Spin spinning={uploadLoading}>
<ContentUploadInput
type="textarea"
beforeUpload={(f) => {
if (f.type !== "text/plain") {
failed(`${f.name}非txt文件,请上传txt格式文件!`)
return false
}
setUploadLoading(true)
ipcRenderer.invoke("fetch-file-content", (f as any).path).then((res) => {
setParams({...params, Target: res})
setTimeout(() => setUploadLoading(false), 100)
})
return false
}}
item={{
style: {textAlign: "left"},
label: "检测的目标",
}}
textarea={{
isBubbing: true,
setValue: (Target) => setParams({...params, Target}),
value: params.Target,
rows: 1,
placeholder: "可接受输入为:URL / IP / 域名 / 主机:端口,逗号分隔"
}}
suffixNode={
executing ? (
<Popconfirm
title={"确定要停止该漏洞检测?"}
onConfirm={(e) => ipcRenderer.invoke("cancel-exec-batch-yak-script", token)}
>
<Button type='primary' danger>
强制停止
</Button>
</Popconfirm>
) : (
<Button type='primary' htmlType='submit'>
开始检测
</Button>
)
}
></ContentUploadInput>
</Spin>
<div style={{width: "100%", textAlign: "left", paddingLeft: 84}}>
<Space>
<Tag>并发/线程: {params.Concurrent}</Tag>
<Tag>总超时: {params.TotalTimeoutSeconds} sec</Tag>
<Button
type={"link"}
style={{margin: 0, paddingLeft: 0}}
onClick={(e) => {
showModal({
title: "设置批量检测额外参数",
content: (
<>
<Form
onSubmitCapture={(e) => e.preventDefault()}
labelCol={{span: 7}}
wrapperCol={{span: 14}}
>
<InputInteger
label={"并发量(线程)"}
setValue={(Concurrent) =>
setParams({...params, Concurrent})
}
defaultValue={params.Concurrent}
/>
<InputInteger
label={"总超时时间/s"}
setValue={(TotalTimeoutSeconds) =>
setParams({
...params,
TotalTimeoutSeconds
})
}
defaultValue={params.TotalTimeoutSeconds}
/>
</Form>
</>
)
})
}}
>
额外参数
</Button>
</Space>
</div>
</Space>
</Form>
</Col>
<Col span={3} style={{position: "relative"}}>
<div style={{width: 140, position: "absolute", right: 2, bottom: 2}}>
<span style={{display: "inline-block", height: 22, marginRight: 5}}>只展示命中项</span>
<Switch checked={checked} onChange={(checked) => setChecked(checked)}></Switch>
</div>
</Col>
</Row>
<Divider style={{margin: "10px 0"}} />
<div ref={listRef} className='bug-test-list'>
{tasks.length === 0 ? (
<div>
<Empty
style={{marginTop: 75}}
description={"模块还未加载,请点击右上角配置进行插件仓库更新"}
></Empty>
</div>
) : checked ? (
<div ref={filterContainerRef} style={{height: listHeight, overflow: "auto"}}>
<div ref={filterWrapperRef}>
{filterList.map((ele) => (
<div className='list-item' key={ele.data.Id}>
<Text ellipsis={{tooltip: true}} copyable={true} style={{width: 260}}>
{ele.data.Id}
</Text>
<Divider type='vertical' />
<div style={{width: 120, textAlign: "center"}}>
{StatusToVerboseTag(ele.data.Status)}
</div>
<Divider type='vertical' />
<div>
<ExecResultsViewer results={ele.data.Results} oneLine={true} />
</div>
<Divider type='vertical' />
<div style={{flexGrow: 1, textAlign: "right"}}>
<Space>
<Button
type={"primary"}
size={"small"}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `单体模块测试: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<YakScriptOperator script={ele.data.PoC} target={params.Target} />
</>
)
})
}}
>
复测
</Button>
<Button
size={"small"}
style={{marginRight: 8}}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `源码: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<div style={{height: 400}}>
<YakEditor
readOnly={true}
type={"yaml"}
value={ele.data.PoC.Content}
/>
</div>
</>
)
})
}}
>
源码
</Button>
</Space>
</div>
</div>
))}
</div>
</div>
) : (
<div ref={containerRef} style={{height: listHeight, overflow: "auto"}}>
<div ref={wrapperRef}>
{list.map((ele) => (
<div className='list-item' key={ele.data.Id}>
<Text ellipsis={{tooltip: true}} copyable={true} style={{width: 260}}>
{ele.data.Id}
</Text>
<Divider type='vertical' />
<div style={{width: 120, textAlign: "center"}}>
{StatusToVerboseTag(ele.data.Status)}
</div>
<Divider type='vertical' />
<div>
<ExecResultsViewer results={ele.data.Results} oneLine={true} />
</div>
<Divider type='vertical' />
<div style={{flexGrow: 1, textAlign: "right"}}>
<Space>
<Button
type={"primary"}
size={"small"}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `单体模块测试: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<YakScriptOperator script={ele.data.PoC} target={params.Target} />
</>
)
})
}}
>
复测
</Button>
<Button
size={"small"}
style={{marginRight: 8}}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `源码: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<div style={{height: 400}}>
<YakEditor
readOnly={true}
type={"yaml"}
value={ele.data.PoC.Content}
/>
</div>
</>
)
})
}}
>
源码
</Button>
</Space>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
)
}
Example #14
Source File: draft.ts From bext with MIT License | 4 votes |
[DraftProvider, useDraft] = constate(() => {
const [cacheDraft, setCacheDraft] = useLocalStorageState<Draft>(
BEXT_DRAFT_KEY,
{ defaultValue: null },
);
const [draft, setDraftObject, getDraftObject] =
useGetState<Draft>(cacheDraft);
const setDraft = useCallback(
(state: Draft) =>
setDraftObject((prev) =>
state === null
? null
: {
...prev,
...state,
// FIXME: 清理存量数据
build: undefined,
options: undefined,
},
),
[],
);
const [clientReady, setClientReady, getClientReady] = useGetState(false);
const injectDraft = useMemoizedFn((content: string = '{}') => {
try {
setDraftObject(JSON.parse(content));
setClientReady(true);
} catch (error) {}
});
useEffect(() => ((window.injectDraft = injectDraft), void 0));
useEffect(() => {
if (isBextClient) {
try {
window.ReactNativeWebView?.postMessage(
JSON.stringify({
type: 'ready',
}),
);
} catch (error) {}
}
}, [isBextClient]);
const saveDraft = useMemoizedFn(() => {
setCacheDraft(draft);
if (isBextClient && getClientReady()) {
try {
window.ReactNativeWebView?.postMessage(
JSON.stringify({
type: 'save',
payload: draft,
}),
);
} catch (error) {}
}
});
useThrottleEffect(
() => {
if (draft) {
saveDraft();
}
},
[draft],
{ wait: 3000 },
);
return {
draft,
setDraft,
setDraftObject,
getDraftObject,
saveDraft,
cacheDraft,
};
})
Example #15
Source File: BatchExecutorPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
BatchExecutorPage: React.FC<BatchExecutorPageProp> = (props) => {
const [loading, setLoading] = useState(false);
const [pluginType, setPluginType] = useState<"yak" | "nuclei" | string>("yak");
const [limit, setLimit] = useState(200);
const [keyword, setKeyword] = useState("");
const [scripts, setScripts, getScripts] = useGetState<YakScript[]>([]);
const [total, setTotal] = useState(0);
const [selected, setSelected] = useState<string[]>([]);
const [indeterminate, setIndeterminate] = useState(false);
const [checked, setChecked] = useState(false)
const [executing, setExecuting] = useState(false);
const [token, setToken] = useState<string>(randomString(40));
const [percent, setPercent] = useState(0.0);
// 处理性能问题
const containerRef = useRef();
const wrapperRef = useRef();
const [list] = useVirtualList(getScripts(), {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 50, overscan: 20,
})
const [vlistHeigth, setVListHeight] = useState(600);
// 执行任务历史列表
const [taskHistory, setTaskHistory] = useState<TaskHistoryProps[]>([])
useEffect(() => {
setLoading(true)
ipcRenderer
.invoke("get-value", ExecuteTaskHistory)
.then((res: any) => {
setTaskHistory(res ? JSON.parse(res) : [])
})
.catch(() => {
})
.finally(() => {
setTimeout(() => setLoading(false), 300)
})
}, [])
useEffect(() => {
const totalYakScript = scripts.length;
const filterArr = scripts.filter((item) => selected.indexOf(item.ScriptName) > -1)
const IndeterminateFlag =
(filterArr.length > 0 && filterArr.length < totalYakScript && selected.length !== 0) ||
(filterArr.length === 0 && selected.length !== 0)
const checkedFlag = filterArr.length === totalYakScript && selected.length !== 0
setIndeterminate(IndeterminateFlag)
setChecked(checkedFlag)
}, [selected, scripts])
const search = useMemoizedFn(() => {
setLoading(true)
queryYakScriptList(
pluginType,
(data, total) => {
setTotal(total || 0)
setScripts(data)
}, () => setTimeout(() => setLoading(false), 300),
limit, undefined, keyword,
(pluginType === "yak" ? {
IsBatch: true
} : {
ExcludeNucleiWorkflow: true,
}) as any,
)
})
useEffect(() => {
setSelected([]);
if (!pluginType) return;
search()
}, [pluginType])
const selectYakScript = useMemoizedFn((y: YakScript) => {
if (!selected.includes(y.ScriptName)) {
setSelected([...selected, y.ScriptName])
}
});
const unselectYakScript = useMemoizedFn((y: YakScript) => {
setSelected(selected.filter(i => i !== y.ScriptName))
})
const renderListItem = useMemoizedFn((y: YakScript) => {
return <YakScriptWithCheckboxLine
key={y.ScriptName}
selected={selected.includes(y.ScriptName)} plugin={y} onSelected={selectYakScript}
onUnselected={unselectYakScript}
/>
});
const run = useMemoizedFn((t: TargetRequest) => {
setPercent(0)
//@ts-ignore
const time = Date.parse(new Date()) / 1000
const obj: TaskHistoryProps = {
target: t,
selected: selected,
pluginType: pluginType,
limit: limit,
keyword: keyword || "",
time: formatTimestamp(time)
}
const arr = [...taskHistory]
if (taskHistory.length === 10) arr.pop()
arr.unshift(obj)
setTaskHistory(arr)
ipcRenderer.invoke("set-value", ExecuteTaskHistory, JSON.stringify(arr))
const tokens = randomString(40)
setToken(tokens)
StartExecBatchYakScript(t, selected, tokens).then(() => {
setExecuting(true)
}).catch(e => {
failed(`启动批量执行插件失败:${e}`)
})
});
const cancel = useMemoizedFn(() => {
CancelBatchYakScript(token).then()
});
useEffect(() => {
ipcRenderer.on(`${token}-data`, async (e, data: any) => {
try {
if (data.ProgressMessage) {
setPercent(data.ProgressPercent)
return
}
} catch (e) {
console.info(e)
}
})
ipcRenderer.on(`${token}-error`, async (e, data) => {
failed(`批量执行插件遇到问题: ${data}`)
})
ipcRenderer.on(`${token}-end`, async (e) => {
setTimeout(() => setExecuting(false), 300)
})
return () => {
ipcRenderer.removeAllListeners(`${token}-data`)
ipcRenderer.removeAllListeners(`${token}-error`)
ipcRenderer.removeAllListeners(`${token}-end`)
}
}, [token])
const executeHistory = useMemoizedFn((item: TaskHistoryProps) => {
setLoading(true)
setLimit(item.limit)
setKeyword(item.keyword)
if (item.pluginType === pluginType) setTimeout(() => search(), 300);
else setPluginType(item.pluginType)
setTimeout(() => {
setSelected(item.selected)
setLoading(false)
}, 300);
})
return <div style={{width: "100%", height: "100%", display: "flex", overflowY: "hidden"}}>
<div style={{width: 470, height: "100%"}}>
{/*<AutoSpin*/}
{/* spinning={loading}*/}
{/*>*/}
<AutoCard
size={"small"}
bordered={false}
title={<Space>
<SelectOne label={"插件"} formItemStyle={{marginBottom: 0}} size={"small"} data={[
{text: "YAK 插件", value: "yak"},
{text: "YAML POC", value: "nuclei"},
]} value={pluginType} setValue={setPluginType}/>
</Space>}
bodyStyle={{
paddingLeft: 4,
paddingRight: 4,
overflow: "hidden", display: "flex", flexDirection: "column",
}}
extra={<Space>
<Popover title={"额外设置"} trigger={["click"]} content={<div>
<Form size={"small"} onSubmitCapture={e => {
e.preventDefault()
search()
}}>
<InputInteger
label={"插件展示数量"} value={limit} setValue={setLimit}
formItemStyle={{marginBottom: 4}}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type="primary" htmlType="submit">刷新</Button>
</Form.Item>
</Form>
</div>}>
<Button size={"small"} icon={<SettingOutlined/>} type={"link"}/>
</Popover>
<Popover title={"搜索插件关键字"} trigger={["click"]} content={<div>
<Form size={"small"} onSubmitCapture={e => {
e.preventDefault()
search()
}}>
<InputItem
label={""}
extraFormItemProps={{style: {marginBottom: 4}, colon: false}}
value={keyword}
setValue={setKeyword}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type="primary" htmlType="submit">搜索</Button>
</Form.Item>
</Form>
</div>}>
<Button size={"small"} type={!!keyword ? "primary" : "link"} icon={<SearchOutlined/>}/>
</Popover>
<Checkbox indeterminate={indeterminate} onChange={(r) => {
if (r.target.checked) {
const newSelected = [...scripts.map(i => i.ScriptName), ...selected];
setSelected(newSelected.filter((e, index) => newSelected.indexOf(e) === index));
} else {
setSelected([]);
}
}} checked={checked}>
全选
</Checkbox>
</Space>}
>
<div style={{flex: "1", overflow: "hidden"}}>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setVListHeight(height)
}}
handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}
/>
<div ref={containerRef as any} style={{height: vlistHeigth, overflow: "auto"}}>
<div ref={wrapperRef as any}>
{list.map(i => renderListItem(i.data))}
</div>
</div>
</div>
</AutoCard>
{/*</AutoSpin>*/}
</div>
<div style={{marginLeft: 12, flex: 1, backgroundColor: "#fff", overflow: "hidden"}}>
<AutoCard
title={<Space>
{"已选插件 / 当页插件 / 插件总量"}
<Tag>{`${selected.length} / ${scripts.length} / ${total}`}</Tag>
</Space>}
size={"small"} bordered={false}
extra={<Space>
{(percent > 0 || executing) && <div style={{width: 200}}>
<Progress status={executing ? "active" : undefined} percent={
parseInt((percent * 100).toFixed(0))
}/>
</div>}
</Space>}
bodyStyle={{display: "flex", flexDirection: "column", padding: '0 5px', overflow: "hidden"}}
>
{/* <ExecSelectedPlugins
disableStartButton={selected.length === 0}
onSubmit={run}
onCancel={cancel}
executing={executing}
loading={loading}
history={taskHistory}
executeHistory={executeHistory}
/> */}
<Divider style={{margin: 4}}/>
<div style={{flex: '1', overflow: "hidden"}}>
<AutoCard style={{padding: 4}} bodyStyle={{padding: 4, overflow: "hidden"}} bordered={false}>
<BatchExecutorResultUI token={token} executing={executing}/>
</AutoCard>
</div>
</AutoCard>
</div>
</div>
}
Example #16
Source File: BatchExecuteByFilter.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
BatchExecutorResultByFilter: React.FC<BatchExecutorResultByFilterProp> = (props) => {
const [activeTask, setActiveTask] = useState<BatchTask[]>([]);
const allPluginTasks = useRef<Map<string, ExecBatchYakScriptResult[]>>(new Map<string, ExecBatchYakScriptResult[]>())
const [allTasks, setAllTasks] = useState<BatchTask[]>([]);
const [hitTasks, setHitTasks] = useState<ExecResultLog[]>([]);
const [taskLog, setTaskLog, getTaskLog] = useGetState<TaskResultLog[]>([]);
const allTasksMap = useCreation<Map<string, BatchTask>>(() => {
return new Map<string, BatchTask>()
}, [])
const [jsonRisks, setJsonRisks] = useState<Risk[]>([]);
const [tableContentHeight, setTableContentHeight] = useState<number>(0);
const [activeKey, setActiveKey] = useState<string>("executing")
useEffect(() => {
if (props.executing && (!!allPluginTasks) && (!!allPluginTasks.current)) allPluginTasks.current.clear()
}, [props.executing])
// 转换task内的result数据
const convertTask = (task: BatchTask) => {
// @ts-ignore
const results: ExecResult[] = task.Results.filter((item) => !!item.Result).map((item) => item.Result)
const messages: ExecResultMessage[] = []
for (let item of results) {
if (!item.IsMessage) continue
try {
const raw = item.Message
const obj: ExecResultMessage = JSON.parse(Buffer.from(raw).toString("utf8"))
messages.push(obj)
} catch (e) {
console.error(e)
}
}
return messages
}
useEffect(() => {
const update = () => {
const result: BatchTask[] = [];
let hitResult: ExecResultLog[] = [];
allTasksMap.forEach(value => {
if (value.Results[value.Results.length - 1]?.Status === "end") {
result.push(value)
if (value.Results.length !== 0) {
const arr: ExecResultLog[] =
(convertTask(value)
.filter((e) => e.type === "log")
.map((i) => i.content) as ExecResultLog[])
.filter((i) => (i?.level || "").toLowerCase() === "json-risk")
if (arr.length > 0) {
hitResult = hitResult.concat(...arr)
}
}
}
})
setAllTasks(result)
setHitTasks(hitResult)
}
update()
const id = setInterval(update, 3000)
return () => {
clearInterval(id)
}
}, [])
useEffect(() => {
let index = 0
const activeTask = new Map<string, ExecBatchYakScriptResult[]>();
ipcRenderer.on(`${props.token}-error`, async (e, exception) => {
if (`${exception}`.includes("Cancelled on client")) {
return
}
console.info("call exception")
console.info(exception)
})
ipcRenderer.on(`${props.token}-data`, async (e, data: ExecBatchYakScriptResult) => {
// 处理进度信息
if (data.ProgressMessage) {
if (!!props.setPercent) {
props.setPercent(data.ProgressPercent || 0)
}
return
}
// 处理其他任务信息
const taskId: string = data.TaskId || "";
if (taskId === "") return
// 缓存内容
let activeResult = activeTask.get(taskId);
if (!activeResult) activeResult = []
activeResult.push(data)
activeTask.set(taskId, activeResult)
// 缓存全部
let allresult = allPluginTasks.current.get(taskId);
if (!allresult) allresult = []
allresult.push(data)
allPluginTasks.current.set(taskId, allresult)
if (data.Result && data.Result.IsMessage) {
const info: TaskResultLog = JSON.parse(new Buffer(data.Result.Message).toString()).content
if (info) {
info.key = index
index += 1
const arr: TaskResultLog[] = [...getTaskLog()]
if (arr.length >= 20) arr.shift()
arr.push(info)
setTaskLog([...arr])
}
}
// 设置状态
if (data.Status === "end") {
activeTask.delete(taskId)
return
}
// 看一下输出结果
// if (data.Result && data.Result.IsMessage) {
// console.info(321,new Buffer(data.Result.Message).toString())
// }
})
let cached = "";
const syncActiveTask = () => {
if (activeTask.size <= 0) setActiveTask([]);
if (activeTask.size <= 0 && allPluginTasks.current.size <= 0) return
const result: BatchTask[] = [];
const tasks: string[] = [];
activeTask.forEach(value => {
if (value.length <= 0) return
const first = value[0];
const task = {
Target: first.Target || "",
ExtraParam: first.ExtraParams || [],
PoC: first.PoC,
TaskId: first.TaskId,
CreatedAt: first.Timestamp,
} as BatchTask;
task.Results = value;
result.push(task)
tasks.push(`${value.length}` + task.TaskId)
})
const allResult: BatchTask[] = [];
allPluginTasks.current.forEach(value => {
if (value.length <= 0) return
const task = {
Target: value[0].Target || "",
ExtraParam: value[0].ExtraParams || [],
PoC: value[0].PoC,
TaskId: value[0].TaskId,
CreatedAt: value[0].Timestamp,
} as BatchTask;
task.Results = value;
allResult.push(task)
})
const oldAllResult: BatchTask[] = []
allTasksMap.forEach(value => oldAllResult.push(value))
if (JSON.stringify(allResult) !== JSON.stringify(oldAllResult)) {
allResult.forEach((value) => allTasksMap.set(value.TaskId, value))
}
const tasksRaw = tasks.sort().join("|")
if (tasksRaw !== cached) {
cached = tasksRaw
setActiveTask(result)
}
}
let id = setInterval(syncActiveTask, 300);
return () => {
ipcRenderer.removeAllListeners(`${props.token}-data`)
ipcRenderer.removeAllListeners(`${props.token}-end`)
ipcRenderer.removeAllListeners(`${props.token}-error`)
allTasksMap.clear()
setTaskLog([])
setAllTasks([])
setActiveKey("executing")
clearInterval(id);
}
}, [props.token])
useEffect(() => {
if (hitTasks.length <= 0) {
return
}
setJsonRisks(hitTasks.map(i => {
try {
return JSON.parse(i.data)
} catch (e) {
return undefined
}
}).filter(i => !!i))
}, [hitTasks])
return <div className="batch-executor-result">
<div className="result-notice-body">
<div className="notice-body">
<div className="notice-body-header notice-font-in-progress">正在执行任务</div>
<div className="notice-body-counter">{activeTask.length}</div>
</div>
<Divider type="vertical" className="notice-divider"/>
<div className="notice-body">
<div className="notice-body-header notice-font-completed">已完成任务</div>
<div className="notice-body-counter">{allTasks.length}</div>
</div>
<Divider type="vertical" className="notice-divider"/>
<div className="notice-body">
<div className="notice-body-header notice-font-vuln">命中风险/漏洞</div>
<div className="notice-body-counter">{jsonRisks.length}</div>
</div>
</div>
<Divider style={{margin: 4}}/>
<div className="result-table-body">
<Tabs className="div-width-height-100 yakit-layout-tabs" activeKey={activeKey} onChange={setActiveKey}>
<Tabs.TabPane tab="任务日志" key={"executing"}>
<div className="div-width-height-100" style={{overflow: "hidden"}}>
<Timeline className="body-time-line" pending={props.executing} reverse={true}>
{taskLog.map(item => {
return <Timeline.Item key={item.key}>
<YakitLogFormatter data={item.data} level={item.level}
timestamp={item.timestamp} onlyTime={true} isCollapsed={true}/>
</Timeline.Item>
})}
</Timeline>
</div>
</Tabs.TabPane>
<Tabs.TabPane tab="命中风险与漏洞" key={"hitTable"}>
<div style={{width: "100%", height: "100%"}}>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) return
setTableContentHeight(height - 4)
}}
handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}/>
<TableResizableColumn
virtualized={true}
sortFilter={() => {
}}
autoHeight={tableContentHeight <= 0}
height={tableContentHeight}
data={jsonRisks}
wordWrap={true}
renderEmpty={() => {
return <Empty className="table-empty" description="数据加载中"/>
}}
columns={[
{
dataKey: "TitleVerbose",
width: 400,
resizable: true,
headRender: () => "标题",
cellRender: ({rowData, dataKey, ...props}: any) => {
return (
<div
className="div-font-ellipsis"
style={{width: "100%"}}
title={rowData?.TitleVerbose || rowData.Title}
>
{rowData?.TitleVerbose || rowData.Title}
</div>
)
}
},
{
dataKey: "RiskTypeVerbose",
width: 130,
headRender: () => "类型",
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData?.RiskTypeVerbose || rowData.RiskType
}
},
{
dataKey: "Severity",
width: 90,
headRender: () => "等级",
cellRender: ({rowData, dataKey, ...props}: any) => {
const title = TitleColor.filter((item) => item.key.includes(rowData.Severity || ""))[0]
return (
<span className={title?.value || "title-default"}>
{title ? title.name : rowData.Severity || "-"}
</span>
)
}
},
{
dataKey: "IP",
width: 140,
headRender: () => "IP",
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData?.IP || "-"
}
},
{
dataKey: "ReverseToken",
headRender: () => "Token",
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData?.ReverseToken || "-"
}
},
{
dataKey: "operate",
width: 90,
fixed: "right",
headRender: () => "操作",
cellRender: ({rowData}: any) => {
return (
<a
onClick={(e) => {
showModal({
width: "80%",
title: "详情",
content: (
<div style={{overflow: "auto"}}>
<RiskDetails info={rowData} isShowTime={false}/>
</div>
)
})
}}
>详情</a>
)
}
}
].map(item => {
item["verticalAlign"] = "middle"
return item
})}
/>
</div>
</Tabs.TabPane>
</Tabs>
</div>
</div>
}
Example #17
Source File: HackerPlugin.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
HackerPlugin: React.FC<HackerPluginProps> = React.memo((props) => {
const [token, setToken] = useState<string>(randomString(40))
const [loading, setLoading] = useState<boolean>(false)
const [lists, setLists, getLists] = useGetState<YakScript[]>([])
const [keyword, setKeyword] = useState<string>("")
const [limit, setLimit] = useState<number>(100)
const [total, setTotal] = useState<number>(0)
const [selected, setSelected] = useState<string[]>([])
const [indeterminate, setIndeterminate] = useState<boolean>(false)
const [checked, setChecked] = useState<boolean>(false)
const containerRef = useRef()
const wrapperRef = useRef()
const [list] = useVirtualList(getLists(), {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 40,
overscan: 20
})
const [vlistHeigth, setVListHeight] = useState(600)
const [execting, setExecting] = useState<boolean>(false)
const [infoState, {reset, setXtermRef}, xtermRef] = useHoldingIPCRStream(
`execute-packet-yak-script`,
"ExecutePacketYakScript",
token,
() => setExecting(false)
)
const search = useMemoizedFn(() => {
setLoading(true)
queryYakScriptList(
"packet-hack",
(data, total) => {
setTotal(total || 0)
setLists(data)
},
() => setTimeout(() => setLoading(false), 300),
limit,
undefined,
keyword
)
})
const selectYakScript = useMemoizedFn((info: YakScript) => {
setSelected([info.ScriptName])
// if (!selected.includes(info.ScriptName)) {
// setSelected([...selected, info.ScriptName])
// }
})
const unselectYakScript = useMemoizedFn((info: YakScript) => {
setSelected([])
// setSelected(selected.filter((i) => i !== info.ScriptName))
})
// useEffect(() => {
// const totalYakScript = lists.length
// const filterArr = lists.filter((item) => selected.indexOf(item.ScriptName) > -1)
// const IndeterminateFlag =
// (filterArr.length > 0 && filterArr.length < totalYakScript && selected.length !== 0) ||
// (filterArr.length === 0 && selected.length !== 0)
// const checkedFlag = filterArr.length === totalYakScript && selected.length !== 0
// setIndeterminate(IndeterminateFlag)
// setChecked(checkedFlag)
// }, [selected, lists])
const startScript = useMemoizedFn(() => {
if (selected.length === 0) {
failed("请选一个插件后在点击执行")
return
}
setExecting(true)
const params: ExecutePacketYakScriptProp = {
ScriptName: selected[0],
IsHttps: props.isHTTPS,
Request: props.request
}
if (!!props.response) params.Response = props.response
ipcRenderer
.invoke("ExecutePacketYakScript", params, token)
.then(() => {})
.catch((e) => {
failed(`Start Packet Checker Error: ${e}`)
setExecting(false)
})
})
const cancelScript = useMemoizedFn(() => {
ipcRenderer.invoke("cancel-ExecutePacketYakScript", token)
})
useEffect(() => {
search()
}, [])
const renderListItem = useMemoizedFn((info: YakScript) => {
return (
<div key={info.ScriptName} className='list-opt'>
<Checkbox
checked={selected.includes(info.ScriptName)}
onChange={(r) => {
if (r.target.checked) selectYakScript(info)
else unselectYakScript(info)
}}
>
<Space>
<Text style={{maxWidth: 270}} ellipsis={{tooltip: true}}>
{info.ScriptName}
</Text>
{info.Help && (
<Button
size={"small"}
type={"link"}
onClick={() => {
showModal({
width: "40%",
title: "Help",
content: <>{info.Help}</>
})
}}
icon={<QuestionCircleOutlined />}
/>
)}
</Space>
</Checkbox>
<div style={{flex: 1, textAlign: "right"}}>
{info.Author && (
<Tooltip title={info.Author}>
<Button size={"small"} type={"link"} icon={<UserOutlined />} />
</Tooltip>
)}
</div>
</div>
)
})
return (
<div className='mitm-exec-plugin'>
<div className='left-body'>
<AutoCard
size='small'
bordered={false}
title={"数据包扫描插件(暂只支持单选)"}
bodyStyle={{padding: "0 4px", overflowY: "hidden"}}
extra={
<Space>
{/* <Checkbox
indeterminate={indeterminate}
onChange={(r) => {
if (r.target.checked) {
const newSelected = [...lists.map((i) => i.ScriptName), ...selected]
setSelected(newSelected.filter((e, index) => newSelected.indexOf(e) === index))
} else {
setSelected([])
}
}}
checked={checked}
>
全选
</Checkbox> */}
<Popover
title={"额外设置"}
trigger={["click"]}
content={
<div>
<Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
search()
}}
>
<InputInteger
label={"插件展示数量"}
value={limit}
setValue={setLimit}
formItemStyle={{marginBottom: 4}}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type='primary' htmlType='submit'>
刷新
</Button>
</Form.Item>
</Form>
</div>
}
>
<Button size={"small"} icon={<SettingOutlined />} type={"link"} />
</Popover>
<Popover
title={"搜索插件关键字"}
trigger={["click"]}
content={
<div>
<Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
search()
}}
>
<InputItem
label={""}
extraFormItemProps={{style: {marginBottom: 4}, colon: false}}
value={keyword}
setValue={setKeyword}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type='primary' htmlType='submit'>
搜索
</Button>
</Form.Item>
</Form>
</div>
}
>
<Button
size={"small"}
type={!!keyword ? "primary" : "link"}
icon={<SearchOutlined />}
/>
</Popover>
{execting ? (
<Button
type='link'
danger
style={{padding: "4px 0"}}
icon={<PoweroffOutlined />}
onClick={cancelScript}
/>
) : (
<Button
type='link'
style={{padding: "4px 0"}}
icon={<CaretRightOutlined />}
onClick={() => {
xtermClear(xtermRef)
reset()
startScript()
}}
/>
)}
</Space>
}
>
<div style={{height: "100%"}}>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setVListHeight(height)
}}
handleWidth={true}
handleHeight={true}
refreshMode={"debounce"}
refreshRate={50}
/>
<div ref={containerRef as any} style={{height: vlistHeigth, overflow: "auto"}}>
<div ref={wrapperRef as any}>{list.map((i) => renderListItem(i.data))}</div>
</div>
</div>
</AutoCard>
</div>
<div className='right-body'>
<AutoCard
size='small'
bordered={false}
title={
<Space>
{"已选插件 / 当页插件 / 插件总量"}
<Tag>{`${selected.length} / ${lists.length} / ${total}`}</Tag>
</Space>
}
bodyStyle={{padding: 0, paddingLeft: 5}}
>
<PluginResultUI
results={infoState.messageState}
progress={infoState.processState}
featureType={infoState.featureTypeState}
feature={infoState.featureMessageState}
statusCards={infoState.statusState}
loading={loading}
onXtermRef={setXtermRef}
/>
</AutoCard>
</div>
</div>
)
})
Example #18
Source File: HTTPFuzzerPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
HTTPFuzzerPage: React.FC<HTTPFuzzerPageProp> = (props) => {
// params
const [isHttps, setIsHttps, getIsHttps] = useGetState<boolean>(props.fuzzerParams?.isHttps || props.isHttps || false)
const [noFixContentLength, setNoFixContentLength] = useState(false)
const [request, setRequest, getRequest] = useGetState(props.fuzzerParams?.request || props.request || defaultPostTemplate)
const [concurrent, setConcurrent] = useState(props.fuzzerParams?.concurrent || 20)
const [forceFuzz, setForceFuzz] = useState<boolean>(props.fuzzerParams?.forceFuzz || true)
const [timeout, setParamTimeout] = useState(props.fuzzerParams?.timeout || 10.0)
const [proxy, setProxy] = useState(props.fuzzerParams?.proxy || "")
const [actualHost, setActualHost] = useState(props.fuzzerParams?.actualHost || "")
const [advancedConfig, setAdvancedConfig] = useState(false)
const [redirectedResponse, setRedirectedResponse] = useState<FuzzerResponse>()
const [historyTask, setHistoryTask] = useState<HistoryHTTPFuzzerTask>();
const [hotPatchCode, setHotPatchCode] = useState<string>("");
// filter
const [_, setFilter, getFilter] = useGetState<FuzzResponseFilter>({
Keywords: [],
MaxBodySize: 0,
MinBodySize: 0,
Regexps: [],
StatusCode: []
});
const [droppedCount, setDroppedCount] = useState(0);
// state
const [loading, setLoading] = useState(false)
const [content, setContent] = useState<FuzzerResponse[]>([])
const [reqEditor, setReqEditor] = useState<IMonacoEditor>()
const [fuzzToken, setFuzzToken] = useState("")
const [search, setSearch] = useState("")
const [targetUrl, setTargetUrl] = useState("")
const [refreshTrigger, setRefreshTrigger] = useState(false)
const refreshRequest = () => {
setRefreshTrigger(!refreshTrigger)
}
// history
const [history, setHistory] = useState<string[]>([])
const [currentHistoryIndex, setCurrentHistoryIndex] = useState<number>()
const [urlPacketShow, setUrlPacketShow] = useState<boolean>(false)
// filter
const [keyword, setKeyword] = useState<string>("")
const [filterContent, setFilterContent] = useState<FuzzerResponse[]>([])
const [timer, setTimer] = useState<any>()
useEffect(() => {
getValue(WEB_FUZZ_HOTPATCH_CODE).then((data: any) => {
if (!data) {
return
}
setHotPatchCode(`${data}`)
})
}, [])
// 定时器
const sendTimer = useRef<any>(null)
const withdrawRequest = useMemoizedFn(() => {
const targetIndex = history.indexOf(request) - 1
if (targetIndex >= 0) {
setRequest(history[targetIndex])
setCurrentHistoryIndex(targetIndex)
}
})
const forwardRequest = useMemoizedFn(() => {
const targetIndex = history.indexOf(request) + 1
if (targetIndex < history.length) {
setCurrentHistoryIndex(targetIndex)
setRequest(history[targetIndex])
}
})
const sendToFuzzer = useMemoizedFn((isHttps: boolean, request: string) => {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {isHttps: isHttps, request: request}
})
})
const sendToPlugin = useMemoizedFn((request: Uint8Array, isHTTPS: boolean, response?: Uint8Array) => {
let m = showDrawer({
width: "80%",
content: <HackerPlugin request={request} isHTTPS={isHTTPS} response={response}></HackerPlugin>
})
})
// 从历史记录中恢复
useEffect(() => {
if (!historyTask) {
return
}
setRequest(historyTask.Request)
setIsHttps(historyTask.IsHTTPS)
setProxy(historyTask.Proxy)
refreshRequest()
}, [historyTask])
useEffect(() => {
// 缓存全局参数
getValue(WEB_FUZZ_PROXY).then(e => {
if (!e) {
return
}
setProxy(`${e}`)
})
}, [])
useEffect(() => {
if (currentHistoryIndex === undefined) {
return
}
refreshRequest()
}, [currentHistoryIndex])
useEffect(() => {
setIsHttps(!!props.isHttps)
if (props.request) {
setRequest(props.request)
setContent([])
}
}, [props.isHttps, props.request])
const loadHistory = useMemoizedFn((id: number) => {
setLoading(true)
setHistory([])
ipcRenderer.invoke(
"HTTPFuzzer",
{HistoryWebFuzzerId: id}, fuzzToken
).then(() => {
ipcRenderer.invoke("GetHistoryHTTPFuzzerTask", {Id: id}).then((data: { OriginRequest: HistoryHTTPFuzzerTask }) => {
setHistoryTask(data.OriginRequest)
})
})
})
const submitToHTTPFuzzer = useMemoizedFn(() => {
// 清楚历史任务的标记
setHistoryTask(undefined);
saveValue(WEB_FUZZ_PROXY, proxy)
setLoading(true)
if (history.includes(request)) {
history.splice(history.indexOf(request), 1)
}
history.push(request)
setHistory([...history])
setDroppedCount(0)
ipcRenderer.invoke(
"HTTPFuzzer",
{
Request: request,
ForceFuzz: forceFuzz,
IsHTTPS: isHttps,
Concurrent: concurrent,
PerRequestTimeoutSeconds: timeout,
NoFixContentLength: noFixContentLength,
Proxy: proxy,
ActualAddr: actualHost,
HotPatchCode: hotPatchCode,
Filter: getFilter(),
},
fuzzToken
)
})
const cancelCurrentHTTPFuzzer = useMemoizedFn(() => {
ipcRenderer.invoke("cancel-HTTPFuzzer", fuzzToken)
})
useEffect(() => {
const token = randomString(60)
setFuzzToken(token)
const dataToken = `${token}-data`
const errToken = `${token}-error`
const endToken = `${token}-end`
ipcRenderer.on(errToken, (e, details) => {
notification["error"]({
message: `提交模糊测试请求失败 ${details}`,
placement: "bottomRight"
})
})
let buffer: FuzzerResponse[] = []
let droppedCount = 0;
let count: number = 0
const updateData = () => {
if (buffer.length <= 0) {
return
}
if (JSON.stringify(buffer) !== JSON.stringify(content)) setContent([...buffer])
}
ipcRenderer.on(dataToken, (e: any, data: any) => {
if (data["MatchedByFilter"] !== true && !filterIsEmpty(getFilter())) {
// 不匹配的
droppedCount++
setDroppedCount(droppedCount)
return
}
// const response = new Buffer(data.ResponseRaw).toString(fixEncoding(data.GuessResponseEncoding))
buffer.push({
StatusCode: data.StatusCode,
Ok: data.Ok,
Reason: data.Reason,
Method: data.Method,
Host: data.Host,
ContentType: data.ContentType,
Headers: (data.Headers || []).map((i: any) => {
return {Header: i.Header, Value: i.Value}
}),
DurationMs: data.DurationMs,
BodyLength: data.BodyLength,
UUID: data.UUID,
Timestamp: data.Timestamp,
ResponseRaw: data.ResponseRaw,
RequestRaw: data.RequestRaw,
Payloads: data.Payloads,
IsHTTPS: data.IsHTTPS,
Count: count,
BodySimilarity: data.BodySimilarity,
HeaderSimilarity: data.HeaderSimilarity,
} as FuzzerResponse)
count++
// setContent([...buffer])
})
ipcRenderer.on(endToken, () => {
updateData()
buffer = []
count = 0
droppedCount = 0
setLoading(false)
})
const updateDataId = setInterval(() => {
updateData()
}, 200)
return () => {
ipcRenderer.invoke("cancel-HTTPFuzzer", token)
clearInterval(updateDataId)
ipcRenderer.removeAllListeners(errToken)
ipcRenderer.removeAllListeners(dataToken)
ipcRenderer.removeAllListeners(endToken)
}
}, [])
const searchContent = (keyword: string) => {
if (timer) {
clearTimeout(timer)
setTimer(null)
}
setTimer(
setTimeout(() => {
try {
const filters = content.filter((item) => {
return Buffer.from(item.ResponseRaw).toString("utf8").match(new RegExp(keyword, "g"))
})
setFilterContent(filters)
} catch (error) {
}
}, 500)
)
}
const downloadContent = useMemoizedFn(() => {
if (!keyword) {
failed('请先输入需要搜索的关键词')
return
}
const strs = []
try {
const reg = new RegExp(keyword)
for (let info of filterContent) {
let str = Buffer.from(info.ResponseRaw).toString('utf8')
let temp: any
while ((temp = reg.exec(str)) !== null) {
// @ts-ignore
if (temp[1]) {
// @ts-ignore
strs.push(temp[1])
str = str.substring(temp['index'] + 1)
reg.lastIndex = 0
} else {
break
}
}
}
} catch (error) {
failed("正则有问题,请检查后重新输入")
return
}
if (strs.length === 0) {
failed('未捕获到关键词信息')
return
}
ipcRenderer.invoke("show-save-dialog", 'fuzzer列表命中内容.txt').then((res) => {
if (res.canceled) return
ipcRenderer
.invoke("write-file", {
route: res.filePath,
data: strs.join('\n\r')
})
.then(() => {
success('下载完成')
ipcRenderer.invoke("open-specified-file", res.filePath)
})
})
})
useEffect(() => {
if (!!keyword) {
searchContent(keyword)
} else {
setFilterContent([])
}
}, [keyword])
useEffect(() => {
if (keyword && content.length !== 0) {
const filters = content.filter(item => {
return Buffer.from(item.ResponseRaw).toString("utf8").match(new RegExp(keyword, 'g'))
})
setFilterContent(filters)
}
}, [content])
const onlyOneResponse = !loading && (content || []).length === 1
const filtredResponses =
search === ""
? content || []
: (content || []).filter((i) => {
return Buffer.from(i.ResponseRaw).toString().includes(search)
})
const successResults = filtredResponses.filter((i) => i.Ok)
const failedResults = filtredResponses.filter((i) => !i.Ok)
const sendFuzzerSettingInfo = useMemoizedFn(() => {
const info: fuzzerInfoProp = {
time: new Date().getTime().toString(),
isHttps: isHttps,
forceFuzz: forceFuzz,
concurrent: concurrent,
proxy: proxy,
actualHost: actualHost,
timeout: timeout,
request: request
}
if (sendTimer.current) {
clearTimeout(sendTimer.current)
sendTimer.current = null
}
sendTimer.current = setTimeout(() => {
ipcRenderer.invoke('send-fuzzer-setting-data', {key: props.order || "", param: JSON.stringify(info)})
}, 1000);
})
useEffect(() => {
sendFuzzerSettingInfo()
}, [isHttps, forceFuzz, concurrent, proxy, actualHost, timeout, request])
const responseViewer = useMemoizedFn((rsp: FuzzerResponse) => {
return (
<HTTPPacketEditor
system={props.system}
originValue={rsp.ResponseRaw}
bordered={true}
hideSearch={true}
emptyOr={
!rsp?.Ok && (
<Result
status={"error"}
title={"请求失败"}
// no such host
subTitle={(() => {
const reason = content[0]!.Reason
if (reason.includes("tcp: i/o timeout")) {
return "网络超时"
}
if (reason.includes("no such host")) {
return "DNS 错误或主机错误"
}
return undefined
})()}
>
<>详细原因:{rsp.Reason}</>
</Result>
)
}
readOnly={true}
extra={
(
<Space>
{loading && <Spin size={"small"} spinning={loading}/>}
{onlyOneResponse ? (
<Space>
{content[0].IsHTTPS && <Tag>{content[0].IsHTTPS ? "https" : ""}</Tag>}
<Tag>{content[0].DurationMs}ms</Tag>
<Space key='single'>
<Button
size={"small"}
onClick={() => {
analyzeFuzzerResponse(
rsp,
(bool, r) => {
// setRequest(r)
// refreshRequest()
}
)
}}
type={"primary"}
icon={<ProfileOutlined/>}
>
详情
</Button>
<Button
type={"primary"}
size={"small"}
onClick={() => {
setContent([])
}}
danger={true}
icon={<DeleteOutlined/>}
/>
</Space>
</Space>
) : (
<Space key='list'>
<Tag color={"green"}>成功:{successResults.length}</Tag>
<Input
size={"small"}
value={search}
onChange={(e) => {
setSearch(e.target.value)
}}
/>
{/*<Tag>当前请求结果数[{(content || []).length}]</Tag>*/}
<Button
size={"small"}
onClick={() => {
setContent([])
}}
>
清除数据
</Button>
</Space>
)}
</Space>
)
}
/>
)
})
const hotPatchTrigger = useMemoizedFn(() => {
let m = showModal({
title: "调试 / 插入热加载代码",
width: "60%",
content: (
<div>
<HTTPFuzzerHotPatch initialHotPatchCode={hotPatchCode || ""} onInsert={tag => {
if (reqEditor) monacoEditorWrite(reqEditor, tag);
m.destroy()
}} onSaveCode={code => {
setHotPatchCode(code)
saveValue(WEB_FUZZ_HOTPATCH_CODE, code)
}}/>
</div>
)
})
})
return (
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column", overflow: "hidden"}}>
<Row gutter={8} style={{marginBottom: 8}}>
<Col span={24} style={{textAlign: "left", marginTop: 4}}>
<Space>
{loading ? (
<Button
style={{width: 150}}
onClick={() => {
cancelCurrentHTTPFuzzer()
}}
// size={"small"}
danger={true}
type={"primary"}
>
强制停止
</Button>
) : (
<Button
style={{width: 150}}
onClick={() => {
setContent([])
setRedirectedResponse(undefined)
sendFuzzerSettingInfo()
submitToHTTPFuzzer()
}}
// size={"small"}
type={"primary"}
>
发送数据包
</Button>
)}
<Space>
<Button
onClick={() => {
withdrawRequest()
}}
type={"link"}
icon={<LeftOutlined/>}
/>
<Button
onClick={() => {
forwardRequest()
}}
type={"link"}
icon={<RightOutlined/>}
/>
{history.length > 1 && (
<Dropdown
trigger={["click"]}
overlay={() => {
return (
<Menu>
{history.map((i, index) => {
return (
<Menu.Item
style={{width: 120}}
onClick={() => {
setRequest(i)
setCurrentHistoryIndex(index)
}}
>{`${index}`}</Menu.Item>
)
})}
</Menu>
)
}}
>
<Button size={"small"} type={"link"} onClick={(e) => e.preventDefault()}>
History <DownOutlined/>
</Button>
</Dropdown>
)}
</Space>
<Checkbox defaultChecked={isHttps} value={isHttps} onChange={() => setIsHttps(!isHttps)}>强制
HTTPS</Checkbox>
<SwitchItem
label={"高级配置"}
formItemStyle={{marginBottom: 0}}
value={advancedConfig}
setValue={setAdvancedConfig}
size={"small"}
/>
{droppedCount > 0 && <Tag color={"red"}>已丢弃[{droppedCount}]个响应</Tag>}
{onlyOneResponse && content[0].Ok && (
<Form.Item style={{marginBottom: 0}}>
<Button
onClick={() => {
setLoading(true)
ipcRenderer
.invoke("RedirectRequest", {
Request: request,
Response: new Buffer(content[0].ResponseRaw).toString("utf8"),
IsHttps: isHttps,
PerRequestTimeoutSeconds: timeout,
NoFixContentLength: noFixContentLength,
Proxy: proxy
})
.then((rsp: FuzzerResponse) => {
setRedirectedResponse(rsp)
})
.catch((e) => {
failed(`"ERROR in: ${e}"`)
})
.finally(() => {
setTimeout(() => setLoading(false), 300)
})
}}
>
跟随重定向
</Button>
</Form.Item>
)}
{loading && (
<Space>
<Spin size={"small"}/>
<div style={{color: "#3a8be3"}}>sending packets</div>
</Space>
)}
{proxy && <Tag>代理:{proxy}</Tag>}
{/*<Popover*/}
{/* trigger={"click"}*/}
{/* content={*/}
{/* }*/}
{/*>*/}
{/* <Button type={"link"} size={"small"}>*/}
{/* 配置请求包*/}
{/* </Button>*/}
{/*</Popover>*/}
{actualHost !== "" && <Tag color={"red"}>请求 Host:{actualHost}</Tag>}
</Space>
</Col>
{/*<Col span={12} style={{textAlign: "left"}}>*/}
{/*</Col>*/}
</Row>
{advancedConfig && (
<Row style={{marginBottom: 8}} gutter={8}>
<Col span={16}>
{/*高级配置*/}
<Card bordered={true} size={"small"} bodyStyle={{height: 106}}>
<Spin style={{width: "100%"}} spinning={!reqEditor}>
<Form
onSubmitCapture={(e) => e.preventDefault()}
// layout={"horizontal"}
size={"small"}
// labelCol={{span: 8}}
// wrapperCol={{span: 16}}
>
<Row gutter={8}>
<Col span={12} xl={8}>
<Form.Item
label={<OneLine width={68}>Intruder</OneLine>}
style={{marginBottom: 4}}
>
<Button
style={{backgroundColor: "#08a701"}}
size={"small"}
type={"primary"}
onClick={() => {
const m = showModal({
width: "70%",
content: (
<>
<StringFuzzer
advanced={true}
disableBasicMode={true}
insertCallback={(template: string) => {
if (!template) {
Modal.warn({
title: "Payload 为空 / Fuzz 模版为空"
})
} else {
if (reqEditor && template) {
reqEditor.trigger(
"keyboard",
"type",
{
text: template
}
)
} else {
Modal.error({
title: "BUG: 编辑器失效"
})
}
m.destroy()
}
}}
/>
</>
)
})
}}
>
插入 yak.fuzz 语法
</Button>
</Form.Item>
</Col>
<Col span={12} xl={8}>
<SwitchItem
label={<OneLine width={68}>渲染 fuzz</OneLine>}
setValue={(e) => {
if (!e) {
Modal.confirm({
title: "确认关闭 Fuzz 功能吗?关闭之后,所有的 Fuzz 标签将会失效",
onOk: () => {
setForceFuzz(e)
}
})
return
}
setForceFuzz(e)
}}
size={"small"}
value={forceFuzz}
formItemStyle={{marginBottom: 4}}
/>
</Col>
<Col span={12} xl={8}>
<InputInteger
label={<OneLine width={68}>并发线程</OneLine>}
size={"small"}
setValue={(e) => {
setConcurrent(e)
}}
formItemStyle={{marginBottom: 4}} // width={40}
width={50}
value={concurrent}
/>
</Col>
<Col span={12} xl={8}>
<SwitchItem
label={<OneLine width={68}>HTTPS</OneLine>}
setValue={(e) => {
setIsHttps(e)
}}
size={"small"}
value={isHttps}
formItemStyle={{marginBottom: 4}}
/>
</Col>
<Col span={12} xl={8}>
<SwitchItem
label={<OneLine width={70}>
<Tooltip title={"不修复 Content-Length: 常用发送多个数据包"}>
不修复长度
</Tooltip>
</OneLine>}
setValue={(e) => {
setNoFixContentLength(e)
}}
size={"small"}
value={noFixContentLength}
formItemStyle={{marginBottom: 4}}
/>
</Col>
<Col span={12} xl={8}>
<ItemSelects
item={{
style: {marginBottom: 4},
label: <OneLine width={68}>设置代理</OneLine>,
}}
select={{
style: {width: "100%"},
allowClear: true,
autoClearSearchValue: true,
maxTagTextLength: 8,
mode: "tags",
data: [
{text: "http://127.0.0.1:7890", value: "http://127.0.0.1:7890"},
{text: "http://127.0.0.1:8080", value: "http://127.0.0.1:8080"},
{text: "http://127.0.0.1:8082", value: "http://127.0.0.1:8082"}
],
value: proxy ? proxy.split(",") : [],
setValue: (value) => setProxy(value.join(",")),
maxTagCount: "responsive",
}}
></ItemSelects>
{/* <ManyMultiSelectForString
formItemStyle={{marginBottom: 4}}
label={<OneLine width={68}>设置代理</OneLine>}
data={[
"http://127.0.0.1:7890",
"http://127.0.0.1:8080",
"http://127.0.0.1:8082"
].map((i) => {
return {label: i, value: i}
})}
mode={"tags"}
defaultSep={","}
value={proxy}
setValue={(r) => {
setProxy(r.split(",").join(","))
}}
/> */}
</Col>
<Col span={12} xl={8}>
<InputItem
extraFormItemProps={{
style: {marginBottom: 0}
}}
label={<OneLine width={68}>请求 Host</OneLine>}
setValue={setActualHost}
value={actualHost}
/>
</Col>
<Col span={12} xl={8}>
<InputFloat
formItemStyle={{marginBottom: 4}}
size={"small"}
label={<OneLine width={68}>超时时间</OneLine>}
setValue={setParamTimeout}
value={timeout}
/>
</Col>
</Row>
</Form>
</Spin>
</Card>
</Col>
<Col span={8}>
<AutoCard title={<Tooltip title={"通过过滤匹配,丢弃无用数据包,保证界面性能!"}>
设置过滤器
</Tooltip>}
bordered={false} size={"small"} bodyStyle={{paddingTop: 4}}
style={{marginTop: 0, paddingTop: 0}}
>
<Form size={"small"} onSubmitCapture={e => e.preventDefault()}>
<Row gutter={20}>
<Col span={12}>
<InputItem
label={"状态码"} placeholder={"200,300-399"}
disable={loading}
value={getFilter().StatusCode.join(",")}
setValue={e => {
setFilter({...getFilter(), StatusCode: e.split(",").filter(i => !!i)})
}}
extraFormItemProps={{style: {marginBottom: 0}}}
/>
</Col>
<Col span={12}>
<InputItem
label={"关键字"} placeholder={"Login,登录成功"}
value={getFilter().Keywords.join(",")}
disable={loading}
setValue={e => {
setFilter({...getFilter(), Keywords: e.split(",").filter(i => !!i)})
}}
extraFormItemProps={{style: {marginBottom: 0}}}
/>
</Col>
<Col span={12}>
<InputItem
label={"正则"} placeholder={`Welcome\\s+\\w+!`}
value={getFilter().Regexps.join(",")}
disable={loading}
setValue={e => {
setFilter({...getFilter(), Regexps: e.split(",").filter(i => !!i)})
}}
extraFormItemProps={{style: {marginBottom: 0, marginTop: 2}}}
/>
</Col>
</Row>
</Form>
</AutoCard>
</Col>
</Row>
)}
{/*<Divider style={{marginTop: 6, marginBottom: 8, paddingTop: 0}}/>*/}
<ResizeBox
firstMinSize={350} secondMinSize={360}
style={{overflow: "hidden"}}
firstNode={<HTTPPacketEditor
system={props.system}
refreshTrigger={refreshTrigger}
hideSearch={true}
bordered={true}
originValue={new Buffer(request)}
actions={[
{
id: "packet-from-url",
label: "URL转数据包",
contextMenuGroupId: "1_urlPacket",
run: () => {
setUrlPacketShow(true)
}
},
{
id: "copy-as-url",
label: "复制为 URL",
contextMenuGroupId: "1_urlPacket",
run: () => {
copyAsUrl({Request: getRequest(), IsHTTPS: getIsHttps()})
}
},
{
id: "insert-intruder-tag",
label: "插入模糊测试字典标签",
contextMenuGroupId: "1_urlPacket",
run: (editor) => {
showDictsAndSelect(i => {
monacoEditorWrite(editor, i, editor.getSelection())
})
}
},
{
id: "insert-hotpatch-tag",
label: "插入热加载标签",
contextMenuGroupId: "1_urlPacket",
run: (editor) => {
hotPatchTrigger()
}
},
]}
onEditor={setReqEditor}
onChange={(i) => setRequest(new Buffer(i).toString("utf8"))}
extra={
<Space size={2}>
<Button
style={{marginRight: 1}}
size={"small"} type={"primary"}
onClick={() => {
hotPatchTrigger()
}}
>热加载标签</Button>
<Popover
trigger={"click"}
title={"从 URL 加载数据包"}
content={
<div style={{width: 400}}>
<Form
layout={"vertical"}
onSubmitCapture={(e) => {
e.preventDefault()
ipcRenderer
.invoke("Codec", {
Type: "packet-from-url",
Text: targetUrl
})
.then((e) => {
if (e?.Result) {
setRequest(e.Result)
refreshRequest()
}
})
.finally(() => {
})
}}
size={"small"}
>
<InputItem
label={"从 URL 构造请求"}
value={targetUrl}
setValue={setTargetUrl}
extraFormItemProps={{style: {marginBottom: 8}}}
></InputItem>
<Form.Item style={{marginBottom: 8}}>
<Button type={"primary"} htmlType={"submit"}>
构造请求
</Button>
</Form.Item>
</Form>
</div>
}
>
<Button size={"small"} type={"primary"}>
URL
</Button>
</Popover>
<Popover
trigger={"click"}
placement={"bottom"}
destroyTooltipOnHide={true}
content={
<div style={{width: 400}}>
<HTTPFuzzerHistorySelector onSelect={e => {
loadHistory(e)
}}/>
</div>
}
>
<Button size={"small"} type={"primary"} icon={<HistoryOutlined/>}>
历史
</Button>
</Popover>
</Space>
}
/>}
secondNode={<AutoSpin spinning={false}>
{onlyOneResponse ? (
<>{redirectedResponse ? responseViewer(redirectedResponse) : responseViewer(content[0])}</>
) : (
<>
{(content || []).length > 0 ? (
<HTTPFuzzerResultsCard
onSendToWebFuzzer={sendToFuzzer}
sendToPlugin={sendToPlugin}
setRequest={(r) => {
setRequest(r)
refreshRequest()
}}
extra={
<div>
<Input
value={keyword}
style={{maxWidth: 200}}
allowClear
placeholder="输入字符串或正则表达式"
onChange={e => setKeyword(e.target.value)}
addonAfter={
<DownloadOutlined style={{cursor: "pointer"}}
onClick={downloadContent}/>
}></Input>
</div>
}
failedResponses={failedResults}
successResponses={filterContent.length !== 0 ? filterContent : keyword ? [] : successResults}
/>
) : (
<Result
status={"info"}
title={"请在左边编辑并发送一个 HTTP 请求/模糊测试"}
subTitle={
"本栏结果针对模糊测试的多个 HTTP 请求结果展示做了优化,可以自动识别单个/多个请求的展示"
}
/>
)}
</>
)}
</AutoSpin>}/>
<Modal
visible={urlPacketShow}
title='从 URL 加载数据包'
onCancel={() => setUrlPacketShow(false)}
footer={null}
>
<Form
layout={"vertical"}
onSubmitCapture={(e) => {
e.preventDefault()
ipcRenderer
.invoke("Codec", {
Type: "packet-from-url",
Text: targetUrl
})
.then((e) => {
if (e?.Result) {
setRequest(e.Result)
refreshRequest()
setUrlPacketShow(false)
}
})
.finally(() => {
})
}}
size={"small"}
>
<InputItem
label={"从 URL 构造请求"}
value={targetUrl}
setValue={setTargetUrl}
extraFormItemProps={{style: {marginBottom: 8}}}
></InputItem>
<Form.Item style={{marginBottom: 8}}>
<Button type={"primary"} htmlType={"submit"}>
构造请求
</Button>
</Form.Item>
</Form>
</Modal>
</div>
)
}
Example #19
Source File: useHoldingIPCRStream.ts From yakit with GNU Affero General Public License v3.0 | 4 votes |
export default function useHoldingIPCRStream(
taskName: string,
apiKey: string,
token: string,
onEnd?: () => any,
onListened?: () => any,
dataFilter?: (obj: ExecResultMessage, content: ExecResultLog) => boolean
) {
const [infoState, setInfoState] = useState<InfoState>({
messageState: [],
processState: [],
statusState: [],
featureMessageState: [],
featureTypeState: []
})
const [xtermRef, setXtermRef, getXtermRef] = useGetState<any>(null)
let messages = useRef<ExecResultMessage[]>([])
let featureMessages = useRef<ExecResultMessage[]>([])
let featureTypes = useRef<ExecResultMessage[]>([])
let processKVPair = useRef<Map<string, number>>(new Map<string, number>())
let statusKVPair = useRef<Map<string, CacheStatusCardProps>>(
new Map<string, CacheStatusCardProps>()
)
useEffect(() => {
const syncResults = () => {
let results = messages.current
.filter((i) => i.type === "log")
.map((i) => i.content as ExecResultLog)
let featureResults = featureMessages.current
.filter((i) => i.type === "log")
.map((i) => i.content as ExecResultLog).filter((i) => i.data !== 'null')
let featureTypeResults = featureTypes.current
.filter((i) => i.type === "log")
.map((i) => i.content as ExecResultLog)
.filter((i) => i.data !== 'null')
const featureTypeFilter = featureTypeResults.map(item => item.data)
featureTypeResults = featureTypeResults.filter((item, index) => featureTypeFilter.indexOf(item.data) === index)
const processes: ExecResultProgress[] = []
processKVPair.current.forEach((value, id) => {
processes.push({ id: id, progress: value })
})
const cacheStatusKVPair: { [x: string]: StatusCardInfoProps } = {}
const statusCards: StatusCardProps[] = []
statusKVPair.current.forEach((value) => {
const item = JSON.parse(JSON.stringify(value))
item.Tag = item.Tags[0] || ""
delete item.Tags
statusCards.push(item)
})
statusCards.sort((a, b) => a.Id.localeCompare(b.Id))
for (let item of statusCards) {
if (item.Tag) {
if (cacheStatusKVPair[item.Tag]) {
cacheStatusKVPair[item.Tag].info.push(item)
} else {
cacheStatusKVPair[item.Tag] = { tag: item.Tag, info: [item] }
}
} else {
cacheStatusKVPair[item.Id] = { tag: item.Id, info: [item] }
}
}
if (
JSON.stringify(infoState) !==
JSON.stringify({
messageState: results,
featureMessageState: featureResults,
processState: processes.sort((a, b) => a.id.localeCompare(b.id)),
statusState: Object.values(cacheStatusKVPair),
featureTypeState: featureTypeResults
})
) {
setInfoState({
messageState: results,
featureMessageState: featureResults,
processState: processes.sort((a, b) => a.id.localeCompare(b.id)),
statusState: Object.values(cacheStatusKVPair),
featureTypeState: featureTypeResults
})
}
}
ipcRenderer.on(`${token}-data`, async (e: any, data: ExecResult) => {
if (data.IsMessage) {
try {
let obj: ExecResultMessage = JSON.parse(
Buffer.from(data.Message).toString("utf8")
)
// 处理 Process KVPair
if (obj.type === "progress") {
const processData = obj.content as ExecResultProgress
if (processData && processData.id) {
processKVPair.current.set(
processData.id,
Math.max(
processKVPair.current.get(processData.id) || 0,
processData.progress
)
)
}
return
}
const logData = obj.content as ExecResultLog
// 处理 log feature-status-card-data
if (obj.type === "log" && logData.level === "feature-status-card-data") {
try {
const obj = JSON.parse(logData.data)
const { id, data, tags } = obj
const { timestamp } = logData
const originData = statusKVPair.current.get(id)
if (originData && originData.Timestamp > timestamp) {
return
}
statusKVPair.current.set(id, {
Id: id,
Data: data,
Timestamp: timestamp,
Tags: Array.isArray(tags) ? tags : []
})
} catch (e) {}
return
}
if (obj.type === "log" && logData.level === "json-feature") {
try {
featureTypes.current.unshift(obj)
} catch (e) {}
return
}
if (obj.type === "log" && logData.level === "feature-table-data") {
try {
featureMessages.current.unshift(obj)
} catch (e) {}
return
}
// 第三方数据过滤方法
if(dataFilter) if(dataFilter(obj, logData)) return
messages.current.unshift(obj)
// 只缓存 100 条结果(日志类型 + 数据类型)
if (messages.current.length > 100) {
messages.current.pop()
}
} catch (e) {}
}
writeExecResultXTerm(getXtermRef(), data)
})
ipcRenderer.on(`${token}-error`, (e: any, error: any) => {
failed(`[Mod] ${taskName} error: ${error}`)
})
ipcRenderer.on(`${token}-end`, (e: any, data: any) => {
info(`[Mod] ${taskName} finished`)
syncResults()
if (onEnd) {
onEnd()
}
})
syncResults()
const time = setInterval(() => syncResults(), 500)
if (onListened) onListened()
return () => {
if (time) clearInterval(time)
ipcRenderer.invoke(`cancel-${apiKey}`, token)
ipcRenderer.removeAllListeners(`${token}-data`)
ipcRenderer.removeAllListeners(`${token}-error`)
ipcRenderer.removeAllListeners(`${token}-end`)
}
}, [])
const reset = () => {
messages.current = []
featureMessages.current = []
featureTypes.current = []
processKVPair.current = new Map<string, number>()
statusKVPair.current = new Map<string, CacheStatusCardProps>()
setInfoState({ messageState: [], processState: [], statusState: [], featureMessageState: [], featureTypeState: [] })
}
return [infoState, { reset, setXtermRef }, xtermRef] as const
}
Example #20
Source File: HTTPFlowTable.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
HTTPFlowTable: React.FC<HTTPFlowTableProp> = (props) => {
const [data, setData, getData] = useGetState<HTTPFlow[]>([])
const [params, setParams] = useState<YakQueryHTTPFlowRequest>(
props.params || {SourceType: "mitm"}
)
const [pagination, setPagination] = useState<PaginationSchema>({
Limit: OFFSET_LIMIT,
Order: "desc",
OrderBy: "created_at",
Page: 1
});
// const [autoReload, setAutoReload, getAutoReload] = useGetState(false);
const autoReloadRef = useRef<boolean>(false);
const autoReload = autoReloadRef.current;
const setAutoReload = (b: boolean) => {
autoReloadRef.current = b
};
const getAutoReload = () => autoReloadRef.current;
const [total, setTotal] = useState<number>(0)
const [loading, setLoading] = useState(false)
const [selected, setSelected, getSelected] = useGetState<HTTPFlow>()
const [_lastSelected, setLastSelected, getLastSelected] = useGetState<HTTPFlow>()
const [compareLeft, setCompareLeft] = useState<CompateData>({content: '', language: 'http'})
const [compareRight, setCompareRight] = useState<CompateData>({content: '', language: 'http'})
const [compareState, setCompareState] = useState(0)
const [tableContentHeight, setTableContentHeight, getTableContentHeight] = useGetState<number>(0);
// 用于记录适合
const [_scrollY, setScrollYRaw, getScrollY] = useGetState(0)
const setScrollY = useThrottleFn(setScrollYRaw, {wait: 300}).run
// 如果这个大于等于 0 ,就 Lock 住,否则忽略
const [_trigger, setLockedScroll, getLockedScroll] = useGetState(-1);
const lockScrollTimeout = (size: number, timeout: number) => {
setLockedScroll(size)
setTimeout(() => setLockedScroll(-1), timeout)
}
const tableRef = useRef(null)
const ref = useHotkeys('ctrl+r, enter', e => {
const selected = getSelected()
if (selected) {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {
isHttps: selected?.IsHTTPS,
request: new Buffer(selected.Request).toString()
}
})
}
})
// 使用上下箭头
useHotkeys("up", () => {
setLastSelected(getSelected())
const data = getData();
if (data.length <= 0) {
return
}
if (!getSelected()) {
setSelected(data[0])
return
}
const expected = parseInt(`${parseInt(`${(getSelected()?.Id as number)}`) + 1}`);
// 如果上点的话,应该是选择更新的内容
for (let i = 0; i < data.length; i++) {
let current = parseInt(`${data[i]?.Id}`);
if (current === expected) {
setSelected(data[i])
return
}
}
setSelected(undefined)
})
useHotkeys("down", () => {
setLastSelected(getSelected())
const data = getData();
if (data.length <= 0) {
return
}
if (!getSelected()) {
setSelected(data[0])
return
}
// 如果上点的话,应该是选择更新的内容
for (let i = 0; i < data.length; i++) {
if (data[i]?.Id == (getSelected()?.Id as number) - 1) {
setSelected(data[i])
return
}
}
setSelected(undefined)
})
// 向主页发送对比数据
useEffect(() => {
if (compareLeft.content) {
const params = {info: compareLeft, type: 1}
setCompareState(compareState === 0 ? 1 : 0)
ipcRenderer.invoke("add-data-compare", params)
}
}, [compareLeft])
useEffect(() => {
if (compareRight.content) {
const params = {info: compareRight, type: 2}
setCompareState(compareState === 0 ? 2 : 0)
ipcRenderer.invoke("add-data-compare", params)
}
}, [compareRight])
const update = useMemoizedFn((
page?: number,
limit?: number,
order?: string,
orderBy?: string,
sourceType?: string,
noLoading?: boolean
) => {
const paginationProps = {
Page: page || 1,
Limit: limit || pagination.Limit,
Order: order || "desc",
OrderBy: orderBy || "id"
}
if (!noLoading) {
setLoading(true)
// setAutoReload(false)
}
// yakQueryHTTPFlow({
// SourceType: sourceType, ...params,
// Pagination: {...paginationProps},
// })
ipcRenderer
.invoke("QueryHTTPFlows", {
SourceType: sourceType,
...params,
Pagination: {...paginationProps}
})
.then((rsp: YakQueryHTTPFlowResponse) => {
setData((rsp?.Data || []))
setPagination(rsp.Pagination)
setTotal(rsp.Total)
})
.catch((e: any) => {
failed(`query HTTP Flow failed: ${e}`)
})
.finally(() => setTimeout(() => setLoading(false), 300))
})
const getNewestId = useMemoizedFn(() => {
let max = 0;
(getData() || []).forEach(e => {
const id = parseInt(`${e.Id}`)
if (id >= max) {
max = id
}
})
return max
})
const getOldestId = useMemoizedFn(() => {
if (getData().length <= 0) {
return 0
}
let min = parseInt(`${getData()[0].Id}`);
(getData() || []).forEach(e => {
const id = parseInt(`${e.Id}`)
if (id <= min) {
min = id
}
})
return min
})
// 第一次启动的时候加载一下
useEffect(() => {
update(1)
}, [])
const scrollTableTo = useMemoizedFn((size: number) => {
if (!tableRef || !tableRef.current) return
const table = tableRef.current as unknown as {
scrollTop: (number) => any,
scrollLeft: (number) => any,
}
table.scrollTop(size)
})
const scrollUpdateTop = useDebounceFn(useMemoizedFn(() => {
const paginationProps = {
Page: 1,
Limit: OFFSET_STEP,
Order: "desc",
OrderBy: "id"
}
const offsetId = getNewestId()
console.info("触顶:", offsetId)
// 查询数据
ipcRenderer
.invoke("QueryHTTPFlows", {
SourceType: "mitm",
...params,
AfterId: offsetId, // 用于计算增量的
Pagination: {...paginationProps}
})
.then((rsp: YakQueryHTTPFlowResponse) => {
const offsetDeltaData = (rsp?.Data || [])
if (offsetDeltaData.length <= 0) {
// 没有增量数据
return
}
setLoading(true)
let offsetData = offsetDeltaData.concat(data);
if (offsetData.length > MAX_ROW_COUNT) {
offsetData = offsetData.splice(0, MAX_ROW_COUNT)
}
setData(offsetData);
scrollTableTo((offsetDeltaData.length + 1) * ROW_HEIGHT)
})
.catch((e: any) => {
failed(`query HTTP Flow failed: ${e}`)
})
.finally(() => setTimeout(() => setLoading(false), 200))
}), {wait: 600, leading: true, trailing: false}).run
const scrollUpdateButt = useDebounceFn(useMemoizedFn((tableClientHeight: number) => {
const paginationProps = {
Page: 1,
Limit: OFFSET_STEP,
Order: "desc",
OrderBy: "id"
}
const offsetId = getOldestId();
console.info("触底:", offsetId)
// 查询数据
ipcRenderer
.invoke("QueryHTTPFlows", {
SourceType: "mitm",
...params,
BeforeId: offsetId, // 用于计算增量的
Pagination: {...paginationProps}
})
.then((rsp: YakQueryHTTPFlowResponse) => {
const offsetDeltaData = (rsp?.Data || [])
if (offsetDeltaData.length <= 0) {
// 没有增量数据
return
}
setLoading(true)
const originDataLength = data.length;
let offsetData = data.concat(offsetDeltaData);
let metMax = false
const originOffsetLength = offsetData.length;
if (originOffsetLength > MAX_ROW_COUNT) {
metMax = true
offsetData = offsetData.splice(originOffsetLength - MAX_ROW_COUNT, MAX_ROW_COUNT)
}
setData(offsetData);
setTimeout(() => {
if (!metMax) {
// 没有丢结果的裁剪问题
scrollTableTo((originDataLength + 1) * ROW_HEIGHT - tableClientHeight)
} else {
// 丢了结果之后的裁剪计算
const a = originOffsetLength - offsetDeltaData.length;
scrollTableTo((originDataLength + 1 + MAX_ROW_COUNT - originOffsetLength) * ROW_HEIGHT - tableClientHeight)
}
}, 50)
})
.catch((e: any) => {
failed(`query HTTP Flow failed: ${e}`)
}).finally(() => setTimeout(() => setLoading(false), 60))
}), {wait: 600, leading: true, trailing: false}).run
const sortFilter = useMemoizedFn((column: string, type: any) => {
const keyRelation: any = {
UpdatedAt: "updated_at",
BodyLength: "body_length",
StatusCode: "status_code"
}
if (column && type) {
update(1, OFFSET_LIMIT, type, keyRelation[column])
} else {
update(1, OFFSET_LIMIT)
}
})
// 这是用来设置选中坐标的,不需要做防抖
useEffect(() => {
if (!getLastSelected() || !getSelected()) {
return
}
const lastSelected = getLastSelected() as HTTPFlow;
const up = parseInt(`${lastSelected?.Id}`) < parseInt(`${selected?.Id}`)
// if (up) {
// console.info("up")
// } else {
// console.info("down")
// }
// console.info(lastSelected.Id, selected?.Id)
const screenRowCount = Math.floor(getTableContentHeight() / ROW_HEIGHT) - 1
if (!autoReload) {
let count = 0;
const data = getData();
for (let i = 0; i < data.length; i++) {
if (data[i].Id != getSelected()?.Id) {
count++
} else {
break
}
}
let minCount = count
if (minCount < 0) {
minCount = 0
}
const viewHeightMin = getScrollY() + tableContentHeight
const viewHeightMax = getScrollY() + tableContentHeight * 2
const minHeight = minCount * ROW_HEIGHT;
const maxHeight = minHeight + tableContentHeight
const maxHeightBottom = minHeight + tableContentHeight + 3 * ROW_HEIGHT
// console.info("top: ", minHeight, "maxHeight: ", maxHeight, "maxHeightBottom: ", maxHeightBottom)
// console.info("viewTop: ", viewHeightMin, "viewButtom: ", viewHeightMax)
if (maxHeight < viewHeightMin) {
// 往下滚动
scrollTableTo(minHeight)
return
}
if (maxHeightBottom > viewHeightMax) {
// 上滚动
const offset = minHeight - (screenRowCount - 2) * ROW_HEIGHT;
// console.info(screenRowCount, minHeight, minHeight - (screenRowCount - 1) * ROW_HEIGHT)
if (offset > 0) {
scrollTableTo(offset)
}
return
}
}
}, [selected])
// 给设置做防抖
useDebounceEffect(() => {
props.onSelected && props.onSelected(selected)
}, [selected], {wait: 400, trailing: true, leading: true})
useEffect(() => {
if (autoReload) {
const id = setInterval(() => {
update(1, undefined, "desc", undefined, undefined, true)
}, 1000)
return () => {
clearInterval(id)
}
}
}, [autoReload])
return (
// <AutoCard bodyStyle={{padding: 0, margin: 0}} bordered={false}>
<div ref={ref as Ref<any>} tabIndex={-1}
style={{width: "100%", height: "100%", overflow: "hidden"}}
>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setTableContentHeight(height - 38)
}}
handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}/>
{!props.noHeader && (
<PageHeader
title={"HTTP History"}
subTitle={
<Space>
{"所有相关请求都在这里"}
<Button
icon={<ReloadOutlined/>}
type={"link"}
onClick={(e) => {
update(1)
}}
/>
</Space>
}
extra={[
<Space>
<Form.Item label={"选择 HTTP History 类型"} style={{marginBottom: 0}}>
<Select
mode={"multiple"}
value={params.SourceType}
style={{minWidth: 200}}
onChange={(e) => {
setParams({...params, SourceType: e})
setLoading(true)
setTimeout(() => {
update(1, undefined, undefined, undefined, e)
}, 200)
}}
>
<Select.Option value={"mitm"}>mitm: 中间人劫持</Select.Option>
<Select.Option value={"fuzzer"}>
fuzzer: 模糊测试分析
</Select.Option>
</Select>
</Form.Item>
<Popconfirm
title={"确定想要删除所有记录吗?不可恢复"}
onConfirm={(e) => {
ipcRenderer.invoke("delete-http-flows-all")
setLoading(true)
info("正在删除...如自动刷新失败请手动刷新")
setTimeout(() => {
update(1)
if (props.onSelected) props.onSelected(undefined)
}, 400)
}}
>
<Button danger={true}>清除全部历史记录?</Button>
</Popconfirm>
</Space>
]}
/>
)}
<Row style={{margin: "5px 0 5px 5px"}}>
<Col span={12}>
<Space>
<span>HTTP History</span>
<Button
icon={<ReloadOutlined/>}
type={"link"}
size={"small"}
onClick={(e) => {
update(1, undefined, "desc")
}}
/>
{/* <Space>
自动刷新:
<Switch size={"small"} checked={autoReload} onChange={setAutoReload}/>
</Space> */}
<Input.Search
placeholder={"URL关键字"}
enterButton={true}
size={"small"}
style={{width: 170}}
value={params.SearchURL}
onChange={(e) => {
setParams({...params, SearchURL: e.target.value})
}}
onSearch={(v) => {
update(1)
}}
/>
{props.noHeader && (
<Popconfirm
title={"确定想要删除所有记录吗?不可恢复"}
onConfirm={(e) => {
ipcRenderer.invoke("delete-http-flows-all")
setLoading(true)
info("正在删除...如自动刷新失败请手动刷新")
setCompareLeft({content: '', language: 'http'})
setCompareRight({content: '', language: 'http'})
setCompareState(0)
setTimeout(() => {
update(1)
if (props.onSelected) props.onSelected(undefined)
}, 400)
}}
>
<Button danger={true} size={"small"}>
删除历史记录
</Button>
</Popconfirm>
)}
{/*{autoReload && <Tag color={"green"}>自动刷新中...</Tag>}*/}
</Space>
</Col>
<Col span={12} style={{textAlign: "right"}}>
<Tag>{total} Records</Tag>
</Col>
</Row>
<TableResizableColumn
tableRef={tableRef}
virtualized={true}
className={"httpFlowTable"}
loading={loading}
columns={[
{
dataKey: "Id",
width: 80,
headRender: () => "序号",
cellRender: ({rowData, dataKey, ...props}: any) => {
return `${rowData[dataKey] <= 0 ? "..." : rowData[dataKey]}`
}
},
{
dataKey: "Method",
width: 70,
headRender: (params1: any) => {
return (
<div
style={{display: "flex", justifyContent: "space-between"}}
>
方法
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"搜索方法"}
params={params}
setParams={setParams}
filterName={"Methods"}
autoCompletions={["GET", "POST", "HEAD"]}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.Methods ? undefined : "gray",
}}
type={!!params.Methods ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
// return (
// <Tag color={"geekblue"} style={{marginRight: 20}}>
// {rowData[dataKey]}
// </Tag>
// )
return rowData[dataKey]
}
},
{
dataKey: "StatusCode",
width: 100,
sortable: true,
headRender: () => {
return (
<div
style={{display: "inline-flex"}}
>
状态码
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"搜索状态码"}
params={params}
setParams={setParams}
filterName={"StatusCode"}
autoCompletions={[
"200",
"300-305",
"400-404",
"500-502",
"200-299",
"300-399",
"400-499"
]}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.StatusCode ? undefined : "gray",
}}
type={!!params.StatusCode ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return (
<div style={{color: StatusCodeToColor(rowData[dataKey])}}>
{rowData[dataKey] === 0 ? "" : rowData[dataKey]}
</div>
)
}
},
{
dataKey: "Url",
resizable: true,
headRender: () => {
return (
<div
style={{display: "flex", justifyContent: "space-between"}}
>
URL
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"搜索URL关键字"}
params={params}
setParams={setParams}
filterName={"SearchURL"}
pureString={true}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.SearchURL ? undefined : "gray",
}}
type={!!params.SearchURL ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
if (rowData.IsPlaceholder) {
return <div style={{color: "#888585"}}>{"滚轮上滑刷新..."}</div>
}
return (
<div style={{width: "100%", display: "flex"}}>
<div className='resize-ellipsis' title={rowData.Url}>
{!params.SearchURL ? (
rowData.Url
) : (
rowData.Url
)}
</div>
</div>
)
},
width: 600
},
{
dataKey: "HtmlTitle",
width: 120,
resizable: true,
headRender: () => {
return "Title"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData[dataKey] ? rowData[dataKey] : ""
}
},
{
dataKey: "Tags",
width: 120,
resizable: true,
headRender: () => {
return "Tags"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData[dataKey] ? (
`${rowData[dataKey]}`.split("|").filter(i => !i.startsWith("YAKIT_COLOR_")).join(", ")
) : ""
}
},
{
dataKey: "IPAddress",
width: 140, resizable: true,
headRender: () => {
return "IP"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData[dataKey] ? rowData[dataKey] : ""
}
},
{
dataKey: "BodyLength",
width: 120,
sortable: true,
headRender: () => {
return (
<div style={{display: "inline-block", position: "relative"}}>
响应长度
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"是否存在Body?"}
params={params}
setParams={setParams}
filterName={"HaveBody"}
pureBool={true}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.HaveBody ? undefined : "gray",
}}
type={!!params.HaveBody ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return (
<div style={{width: 100}}>
{/* 1M 以上的话,是红色*/}
{rowData.BodyLength !== -1 ?
(<div style={{color: rowData.BodyLength > 1000000 ? "red" : undefined}}>
{rowData.BodySizeVerbose
? rowData.BodySizeVerbose
: rowData.BodyLength}
</div>)
:
(<div></div>)
}
</div>
)
}
},
// {
// dataKey: "UrlLength",
// width: 90,
// headRender: () => {
// return "URL 长度"
// },
// cellRender: ({rowData, dataKey, ...props}: any) => {
// const len = (rowData.Url || "").length
// return len > 0 ? <div>{len}</div> : "-"
// }
// },
{
dataKey: "GetParamsTotal",
width: 65,
align: "center",
headRender: () => {
return (
<div
style={{display: "flex", justifyContent: "space-between"}}
>
参数
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"过滤是否存在基础参数"}
params={params}
setParams={setParams}
filterName={"HaveCommonParams"}
pureBool={true}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.HaveCommonParams ? undefined : "gray",
}}
type={!!params.HaveCommonParams ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return (
<Space>
{(rowData.GetParamsTotal > 0 ||
rowData.PostParamsTotal > 0) && <CheckOutlined/>}
</Space>
)
}
},
{
dataKey: "ContentType",
resizable: true, width: 80,
headRender: () => {
return "响应类型"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
let contentTypeFixed = rowData.ContentType.split(";")
.map((el: any) => el.trim())
.filter((i: any) => !i.startsWith("charset"))
.join(",") || "-"
if (contentTypeFixed.includes("/")) {
const contentTypeFixedNew = contentTypeFixed.split("/").pop()
if (!!contentTypeFixedNew) {
contentTypeFixed = contentTypeFixedNew
}
}
return (
<div>
{contentTypeFixed === "null" ? "" : contentTypeFixed}
</div>
)
}
},
{
dataKey: "UpdatedAt",
sortable: true,
width: 110,
headRender: () => {
return "请求时间"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return <Tooltip
title={rowData[dataKey] === 0 ? "" : formatTimestamp(rowData[dataKey])}
>
{rowData[dataKey] === 0 ? "" : formatTime(rowData[dataKey])}
</Tooltip>
}
},
{
dataKey: "operate",
width: 90,
headRender: () => "操作",
cellRender: ({rowData}: any) => {
if (!rowData.Hash) return <></>
return (
<a
onClick={(e) => {
let m = showDrawer({
width: "80%",
content: onExpandHTTPFlow(
rowData,
() => m.destroy()
)
})
}}
>
详情
</a>
)
}
}
]}
data={autoReload ? data : [TableFirstLinePlaceholder].concat(data)}
autoHeight={tableContentHeight <= 0}
height={tableContentHeight}
sortFilter={sortFilter}
renderRow={(children: ReactNode, rowData: any) => {
if (rowData)
return (
<div
id='http-flow-row'
ref={(node) => {
const color =
rowData.Hash === selected?.Hash ?
"rgba(78, 164, 255, 0.4)" :
rowData.Tags.indexOf("YAKIT_COLOR") > -1 ?
TableRowColor(rowData.Tags.split("|").pop().split('_').pop().toUpperCase()) :
"#ffffff"
if (node) {
if (color) node.style.setProperty("background-color", color, "important")
else node.style.setProperty("background-color", "#ffffff")
}
}}
style={{height: "100%"}}
>
{children}
</div>
)
return children
}}
onRowContextMenu={(rowData: HTTPFlow | any, event: React.MouseEvent) => {
if (rowData) {
setSelected(rowData);
}
showByCursorMenu(
{
content: [
{
title: '发送到 Web Fuzzer',
onClick: () => {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {
isHttps: rowData.IsHTTPS,
request: new Buffer(rowData.Request).toString("utf8")
}
})
}
},
{
title: '发送到 数据包扫描',
onClick: () => {
ipcRenderer
.invoke("GetHTTPFlowByHash", {Hash: rowData.Hash})
.then((i: HTTPFlow) => {
ipcRenderer.invoke("send-to-packet-hack", {
request: i.Request,
ishttps: i.IsHTTPS,
response: i.Response
})
})
.catch((e: any) => {
failed(`Query Response failed: ${e}`)
})
}
},
{
title: '复制 URL',
onClick: () => {
callCopyToClipboard(rowData.Url)
},
},
{
title: '复制为 Yak PoC 模版', onClick: () => {
},
subMenuItems: [
{
title: "数据包 PoC 模版", onClick: () => {
const flow = rowData as HTTPFlow;
if (!flow) return;
generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
callCopyToClipboard(code)
}, RequestToYakCodeTemplate.Ordinary)
}
},
{
title: "批量检测 PoC 模版", onClick: () => {
const flow = rowData as HTTPFlow;
if (!flow) return;
generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
callCopyToClipboard(code)
}, RequestToYakCodeTemplate.Batch)
}
},
]
},
{
title: '标注颜色',
subMenuItems: availableColors.map(i => {
return {
title: i.title,
render: i.render,
onClick: () => {
const flow = rowData as HTTPFlow
if (!flow) {
return
}
const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
existedTags.push(`YAKIT_COLOR_${i.color.toUpperCase()}`)
ipcRenderer.invoke("SetTagForHTTPFlow", {
Id: flow.Id, Hash: flow.Hash,
Tags: existedTags,
}).then(() => {
info(`设置 HTTPFlow 颜色成功`)
if (!autoReload) {
setData(data.map(item => {
if (item.Hash === flow.Hash) {
item.Tags = `YAKIT_COLOR_${i.color.toUpperCase()}`
return item
}
return item
}))
}
})
}
}
}),
onClick: () => {
}
},
{
title: '移除颜色',
onClick: () => {
const flow = rowData as HTTPFlow
if (!flow) return
const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
existedTags.pop()
ipcRenderer.invoke("SetTagForHTTPFlow", {
Id: flow.Id, Hash: flow.Hash,
Tags: existedTags,
}).then(() => {
info(`清除 HTTPFlow 颜色成功`)
if (!autoReload) {
setData(data.map(item => {
if (item.Hash === flow.Hash) {
item.Tags = ""
return item
}
return item
}))
}
})
return
},
},
{
title: "发送到对比器", onClick: () => {
},
subMenuItems: [
{
title: '发送到对比器左侧',
onClick: () => {
setCompareLeft({
content: new Buffer(rowData.Request).toString("utf8"),
language: 'http'
})
},
disabled: [false, true, false][compareState]
},
{
title: '发送到对比器右侧',
onClick: () => {
setCompareRight({
content: new Buffer(rowData.Request).toString("utf8"),
language: 'http'
})
},
disabled: [false, false, true][compareState]
}
]
},
]
},
event.clientX,
event.clientY
)
}}
onRowClick={(rowDate: any) => {
if (!rowDate.Hash) return
if (rowDate.Hash !== selected?.Hash) {
setSelected(rowDate)
} else {
// setSelected(undefined)
}
}}
onScroll={(scrollX, scrollY) => {
setScrollY(scrollY)
// 防止无数据触发加载
if (data.length === 0 && !getAutoReload()) {
setAutoReload(true)
return
}
// 根据页面展示内容决定是否自动刷新
let contextHeight = (data.length + 1) * ROW_HEIGHT // +1 是要把表 title 算进去
let offsetY = scrollY + tableContentHeight;
if (contextHeight < tableContentHeight) {
setAutoReload(true)
return
}
setAutoReload(false)
// 向下刷新数据
if (contextHeight <= offsetY) {
setAutoReload(false)
scrollUpdateButt(tableContentHeight)
return
}
// 锁住滚轮
if (getLockedScroll() > 0 && getLockedScroll() >= scrollY) {
if (scrollY === getLockedScroll()) {
return
}
// scrollTableTo(getLockedScroll())
return
}
const toTop = scrollY <= 0;
if (toTop) {
lockScrollTimeout(ROW_HEIGHT, 600)
scrollUpdateTop()
}
}}
/>
</div>
// </AutoCard>
)
}