feat(ui): add renderers for ja4h, http2/3 settings, ja4-quic fingerprints
FingerprintGroup switch fell through to FpGeneric (raw JSON dump) for all four new fingerprint_type values the ingester now produces. Add FpJa4h, FpHttpSettings, FpJa4Quic components and wire them into the dispatcher; also register their labels and icons in fpTypeLabel/fpTypeIcon.
This commit is contained in:
@@ -6,12 +6,16 @@ import {
|
|||||||
export const fpTypeLabel: Record<string, string> = {
|
export const fpTypeLabel: Record<string, string> = {
|
||||||
ja3: 'TLS FINGERPRINT',
|
ja3: 'TLS FINGERPRINT',
|
||||||
ja4l: 'LATENCY (JA4L)',
|
ja4l: 'LATENCY (JA4L)',
|
||||||
|
ja4h: 'JA4H (HTTP)',
|
||||||
|
ja4_quic: 'JA4-QUIC',
|
||||||
tls_resumption: 'SESSION RESUMPTION',
|
tls_resumption: 'SESSION RESUMPTION',
|
||||||
tls_certificate: 'CERTIFICATE',
|
tls_certificate: 'CERTIFICATE',
|
||||||
tls_certificate_active: 'CERTIFICATE (ACTIVE PROBE)',
|
tls_certificate_active: 'CERTIFICATE (ACTIVE PROBE)',
|
||||||
tls_certificate_passive: 'CERTIFICATE',
|
tls_certificate_passive: 'CERTIFICATE',
|
||||||
http_useragent: 'HTTP USER-AGENT',
|
http_useragent: 'HTTP USER-AGENT',
|
||||||
http_quirks: 'HTTP HEADER QUIRKS',
|
http_quirks: 'HTTP HEADER QUIRKS',
|
||||||
|
http2_settings: 'HTTP/2 SETTINGS',
|
||||||
|
http3_settings: 'HTTP/3 SETTINGS',
|
||||||
spoofed_source: 'SPOOFED SOURCE IP',
|
spoofed_source: 'SPOOFED SOURCE IP',
|
||||||
vnc_client_version: 'VNC CLIENT',
|
vnc_client_version: 'VNC CLIENT',
|
||||||
jarm: 'JARM',
|
jarm: 'JARM',
|
||||||
@@ -22,12 +26,16 @@ export const fpTypeLabel: Record<string, string> = {
|
|||||||
export const fpTypeIcon: Record<string, React.ReactNode> = {
|
export const fpTypeIcon: Record<string, React.ReactNode> = {
|
||||||
ja3: <Fingerprint size={14} />,
|
ja3: <Fingerprint size={14} />,
|
||||||
ja4l: <Clock size={14} />,
|
ja4l: <Clock size={14} />,
|
||||||
|
ja4h: <Fingerprint size={14} />,
|
||||||
|
ja4_quic: <Crosshair size={14} />,
|
||||||
tls_resumption: <Wifi size={14} />,
|
tls_resumption: <Wifi size={14} />,
|
||||||
tls_certificate: <FileKey size={14} />,
|
tls_certificate: <FileKey size={14} />,
|
||||||
tls_certificate_active: <FileKey size={14} />,
|
tls_certificate_active: <FileKey size={14} />,
|
||||||
tls_certificate_passive: <FileKey size={14} />,
|
tls_certificate_passive: <FileKey size={14} />,
|
||||||
http_useragent: <Shield size={14} />,
|
http_useragent: <Shield size={14} />,
|
||||||
http_quirks: <Fingerprint size={14} />,
|
http_quirks: <Fingerprint size={14} />,
|
||||||
|
http2_settings: <Wifi size={14} />,
|
||||||
|
http3_settings: <Wifi size={14} />,
|
||||||
spoofed_source: <Crosshair size={14} />,
|
spoofed_source: <Crosshair size={14} />,
|
||||||
vnc_client_version: <Lock size={14} />,
|
vnc_client_version: <Lock size={14} />,
|
||||||
jarm: <Crosshair size={14} />,
|
jarm: <Crosshair size={14} />,
|
||||||
|
|||||||
@@ -207,6 +207,77 @@ export const FpTcpStack: React.FC<{ p: any }> = ({ p }) => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const FpJa4h: React.FC<{ p: any }> = ({ p }) => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||||
|
<HashRow label="JA4H" value={String(p.ja4h ?? '')} />
|
||||||
|
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||||
|
{p.protocol && <Tag>{String(p.protocol).toUpperCase()}</Tag>}
|
||||||
|
{p.method && <Tag color="var(--accent-color)">{String(p.method).toUpperCase()}</Tag>}
|
||||||
|
{p.path && <Tag>{String(p.path)}</Tag>}
|
||||||
|
{p.remote_port && <Tag>:{p.remote_port}</Tag>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const FpHttpSettings: React.FC<{ p: any }> = ({ p }) => {
|
||||||
|
let entries: [string, unknown][] = [];
|
||||||
|
if (p.settings) {
|
||||||
|
try {
|
||||||
|
const parsed = typeof p.settings === 'string' ? JSON.parse(p.settings) : p.settings;
|
||||||
|
entries = Object.entries(parsed as Record<string, unknown>);
|
||||||
|
} catch { /* leave entries empty */ }
|
||||||
|
}
|
||||||
|
let frameOrder: string[] = [];
|
||||||
|
if (p.frame_order) {
|
||||||
|
try {
|
||||||
|
const parsed = typeof p.frame_order === 'string' ? JSON.parse(p.frame_order) : p.frame_order;
|
||||||
|
if (Array.isArray(parsed)) frameOrder = parsed.map(String);
|
||||||
|
} catch { /* leave empty */ }
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||||
|
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
|
{p.protocol && <Tag>{String(p.protocol).toUpperCase()}</Tag>}
|
||||||
|
{p.remote_port && <Tag>:{p.remote_port}</Tag>}
|
||||||
|
</div>
|
||||||
|
{entries.length > 0 && (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '3px' }}>
|
||||||
|
{entries.map(([k, v]) => (
|
||||||
|
<div key={k} style={{ display: 'flex', gap: '8px', alignItems: 'baseline' }}>
|
||||||
|
<span className="dim" style={{ fontSize: '0.7rem', minWidth: '180px' }}>
|
||||||
|
{k.replace(/_/g, ' ')}
|
||||||
|
</span>
|
||||||
|
<span className="matrix-text" style={{ fontFamily: 'monospace', fontSize: '0.8rem' }}>
|
||||||
|
{String(v)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{frameOrder.length > 0 && (
|
||||||
|
<details>
|
||||||
|
<summary className="dim" style={{ fontSize: '0.7rem', cursor: 'pointer', letterSpacing: '1px' }}>
|
||||||
|
FRAME ORDER
|
||||||
|
</summary>
|
||||||
|
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap', marginTop: '4px' }}>
|
||||||
|
{frameOrder.map((f, i) => <Tag key={i}>{f}</Tag>)}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FpJa4Quic: React.FC<{ p: any }> = ({ p }) => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||||
|
<HashRow label="JA4-QUIC" value={String(p.ja4_quic ?? '')} />
|
||||||
|
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||||
|
{p.sni && <Tag color="var(--accent-color)">SNI: {p.sni}</Tag>}
|
||||||
|
{p.alpn && <Tag>ALPN: {p.alpn}</Tag>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
export const FpGeneric: React.FC<{ p: any }> = ({ p }) => (
|
export const FpGeneric: React.FC<{ p: any }> = ({ p }) => (
|
||||||
<div>
|
<div>
|
||||||
{p.value ? (
|
{p.value ? (
|
||||||
@@ -353,6 +424,11 @@ export const FingerprintGroup: React.FC<{ fpType: string; items: any[] }> = ({ f
|
|||||||
case 'http_quirks': return <FpHttpQuirks key={i} p={p} />;
|
case 'http_quirks': return <FpHttpQuirks key={i} p={p} />;
|
||||||
case 'http_useragent': return <FpUserAgent key={i} p={p} />;
|
case 'http_useragent': return <FpUserAgent key={i} p={p} />;
|
||||||
case 'spoofed_source': return <FpSpoofedSource key={i} p={p} />;
|
case 'spoofed_source': return <FpSpoofedSource key={i} p={p} />;
|
||||||
|
case 'ja4h': return <FpJa4h key={i} p={p} />;
|
||||||
|
case 'http2_settings':
|
||||||
|
case 'http3_settings':
|
||||||
|
return <FpHttpSettings key={i} p={p} />;
|
||||||
|
case 'ja4_quic': return <FpJa4Quic key={i} p={p} />;
|
||||||
default: return <FpGeneric key={i} p={p} />;
|
default: return <FpGeneric key={i} p={p} />;
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user