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 vote down vote up
// 关于获取函数还有点问题,暂不可用
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
[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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
    )
}