mirror of
https://github.com/Lissy93/web-check.git
synced 2025-03-13 14:39:48 +01:00
Merge pull request #41 from Lissy93/FEAT/tls-and-refactor
Feat/tls and refactor
This commit is contained in:
commit
95a63825f0
47
api/ssl.js
47
api/ssl.js
@ -1,43 +1,40 @@
|
||||
const https = require('https');
|
||||
const tls = require('tls');
|
||||
const middleware = require('./_common/middleware');
|
||||
const urlModule = require('url');
|
||||
|
||||
const fetchSiteCertificateHandler = async (urlString) => {
|
||||
try {
|
||||
const parsedUrl = urlModule.parse(urlString);
|
||||
const parsedUrl = new URL(urlString);
|
||||
const options = {
|
||||
host: parsedUrl.hostname,
|
||||
port: parsedUrl.port || 443, // Default port for HTTPS
|
||||
method: 'GET',
|
||||
servername: parsedUrl.hostname, // For SNI
|
||||
rejectUnauthorized: false // Disable strict SSL verification (use with caution)
|
||||
port: parsedUrl.port || 443,
|
||||
servername: parsedUrl.hostname,
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const req = https.request(options, res => {
|
||||
|
||||
// Check if the SSL handshake was authorized
|
||||
if (!res.socket.authorized) {
|
||||
reject(new Error(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`));
|
||||
} else {
|
||||
let cert = res.socket.getPeerCertificate(true);
|
||||
if (!cert || Object.keys(cert).length === 0) {
|
||||
reject(new Error("No certificate presented by the server."));
|
||||
} else {
|
||||
const { raw, issuerCertificate, ...certWithoutRaw } = cert;
|
||||
resolve(certWithoutRaw);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = tls.connect(options, () => {
|
||||
if (!socket.authorized) {
|
||||
return reject(new Error(`SSL handshake not authorized. Reason: ${socket.authorizationError}`));
|
||||
}
|
||||
|
||||
const cert = socket.getPeerCertificate();
|
||||
if (!cert || Object.keys(cert).length === 0) {
|
||||
return reject(new Error(`
|
||||
No certificate presented by the server.\n
|
||||
The server is possibly not using SNI (Server Name Indication) to identify itself, and you are connecting to a hostname-aliased IP address.
|
||||
Or it may be due to an invalid SSL certificate, or an incomplete SSL handshake at the time the cert is being read.`));
|
||||
}
|
||||
|
||||
const { raw, issuerCertificate, ...certWithoutRaw } = cert;
|
||||
resolve(certWithoutRaw);
|
||||
socket.end();
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
socket.on('error', (error) => {
|
||||
reject(new Error(`Error fetching site certificate: ${error.message}`));
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
28
api/tls.js
Normal file
28
api/tls.js
Normal file
@ -0,0 +1,28 @@
|
||||
const axios = require('axios');
|
||||
const middleware = require('./_common/middleware');
|
||||
|
||||
const MOZILLA_TLS_OBSERVATORY_API = 'https://tls-observatory.services.mozilla.com/api/v1';
|
||||
|
||||
const handler = async (url) => {
|
||||
try {
|
||||
const domain = new URL(url).hostname;
|
||||
const scanResponse = await axios.post(`${MOZILLA_TLS_OBSERVATORY_API}/scan?target=${domain}`);
|
||||
const scanId = scanResponse.data.scan_id;
|
||||
|
||||
if (typeof scanId !== 'number') {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: 'Failed to get scan_id from TLS Observatory' }),
|
||||
};
|
||||
}
|
||||
const resultResponse = await axios.get(`${MOZILLA_TLS_OBSERVATORY_API}/results?id=${scanId}`);
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(resultResponse.data),
|
||||
};
|
||||
} catch (error) {
|
||||
return { error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
exports.handler = middleware(handler);
|
@ -1,7 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import colors from 'styles/colors';
|
||||
import { InputSize, applySize } from 'styles/dimensions';
|
||||
|
||||
type LoadState = 'loading' | 'success' | 'error';
|
||||
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
@ -10,6 +12,7 @@ interface ButtonProps {
|
||||
fgColor?: string,
|
||||
styles?: string,
|
||||
title?: string,
|
||||
loadState?: LoadState,
|
||||
};
|
||||
|
||||
const StyledButton = styled.button<ButtonProps>`
|
||||
@ -19,6 +22,9 @@ const StyledButton = styled.button<ButtonProps>`
|
||||
font-family: PTMono;
|
||||
box-sizing: border-box;
|
||||
width: -moz-available;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
box-shadow: 3px 3px 0px ${colors.fgShadowColor};
|
||||
&:hover {
|
||||
box-shadow: 5px 5px 0px ${colors.fgShadowColor};
|
||||
@ -36,8 +42,29 @@ const StyledButton = styled.button<ButtonProps>`
|
||||
${props => props.styles}
|
||||
`;
|
||||
|
||||
|
||||
const spinAnimation = keyframes`
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
`;
|
||||
const SimpleLoader = styled.div`
|
||||
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top: 4px solid ${colors.background};
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
animation: ${spinAnimation} 1s linear infinite;
|
||||
`;
|
||||
|
||||
const Loader = (props: { loadState: LoadState }) => {
|
||||
if (props.loadState === 'loading') return <SimpleLoader />
|
||||
if (props.loadState === 'success') return <span>✔</span>
|
||||
if (props.loadState === 'error') return <span>✗</span>
|
||||
return <span></span>;
|
||||
};
|
||||
|
||||
const Button = (props: ButtonProps): JSX.Element => {
|
||||
const { children, size, bgColor, fgColor, onClick, styles, title } = props;
|
||||
const { children, size, bgColor, fgColor, onClick, styles, title, loadState } = props;
|
||||
return (
|
||||
<StyledButton
|
||||
onClick={onClick || (() => null) }
|
||||
@ -47,6 +74,7 @@ const Button = (props: ButtonProps): JSX.Element => {
|
||||
styles={styles}
|
||||
title={title?.toString()}
|
||||
>
|
||||
{ loadState && <Loader loadState={loadState} /> }
|
||||
{children}
|
||||
</StyledButton>
|
||||
);
|
||||
|
@ -28,6 +28,7 @@ const StyledHeading = styled.h1<HeadingProps>`
|
||||
a { // If a title is a link, keep title styles
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
}
|
||||
${props => {
|
||||
switch (props.size) {
|
||||
|
@ -11,11 +11,14 @@ export interface RowProps {
|
||||
rowList?: RowProps[],
|
||||
title?: string,
|
||||
open?: boolean,
|
||||
plaintext?: string,
|
||||
listResults?: string[],
|
||||
}
|
||||
|
||||
export const StyledRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.25rem;
|
||||
&:not(:last-child) { border-bottom: 1px solid ${colors.primary}; }
|
||||
span.lbl { font-weight: bold; }
|
||||
@ -38,6 +41,7 @@ export const Details = styled.details`
|
||||
transition: all 0.2s ease-in-out;
|
||||
summary {
|
||||
padding-left: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
summary:before {
|
||||
content: "►";
|
||||
@ -63,6 +67,37 @@ const SubRow = styled(StyledRow).attrs({
|
||||
border-bottom: 1px dashed ${colors.primaryTransparent} !important;
|
||||
`;
|
||||
|
||||
const PlainText = styled.pre`
|
||||
background: ${colors.background};
|
||||
width: 95%;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
border-radius: 4px;
|
||||
padding: 0.25rem;
|
||||
`;
|
||||
|
||||
const List = styled.ul`
|
||||
// background: ${colors.background};
|
||||
width: 95%;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
padding: 0.25rem 0.25rem 0.25rem 1rem;
|
||||
li {
|
||||
// white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
list-style: circle;
|
||||
&:first-letter{
|
||||
text-transform: capitalize
|
||||
}
|
||||
&::marker {
|
||||
color: ${colors.primary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const isValidDate = (date: any): boolean => {
|
||||
// Checks if a date is within reasonable range
|
||||
const isInRange = (date: Date): boolean => {
|
||||
@ -106,6 +141,11 @@ const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
const snip = (text: string, length: number = 80) => {
|
||||
if (text.length < length) return text;
|
||||
return `${text.substring(0, length)}...`;
|
||||
};
|
||||
|
||||
export const ExpandableRow = (props: RowProps) => {
|
||||
const { lbl, val, title, rowList, open } = props;
|
||||
return (
|
||||
@ -123,6 +163,12 @@ export const ExpandableRow = (props: RowProps) => {
|
||||
<span className="val" title={row.val} onClick={() => copyToClipboard(row.val)}>
|
||||
{formatValue(row.val)}
|
||||
</span>
|
||||
{ row.plaintext && <PlainText>{row.plaintext}</PlainText> }
|
||||
{ row.listResults && (<List>
|
||||
{row.listResults.map((listItem: string, listIndex: number) => (
|
||||
<li key={listItem}>{snip(listItem)}</li>
|
||||
))}
|
||||
</List>)}
|
||||
</SubRow>
|
||||
)
|
||||
})}
|
||||
@ -149,7 +195,7 @@ export const ListRow = (props: { list: string[], title: string }) => {
|
||||
}
|
||||
|
||||
const Row = (props: RowProps) => {
|
||||
const { lbl, val, title, children } = props;
|
||||
const { lbl, val, title, plaintext, listResults, children } = props;
|
||||
if (children) return <StyledRow key={`${lbl}-${val}`}>{children}</StyledRow>;
|
||||
return (
|
||||
<StyledRow key={`${lbl}-${val}`}>
|
||||
@ -157,6 +203,12 @@ const Row = (props: RowProps) => {
|
||||
<span className="val" title={val} onClick={() => copyToClipboard(val)}>
|
||||
{formatValue(val)}
|
||||
</span>
|
||||
{ plaintext && <PlainText>{plaintext}</PlainText> }
|
||||
{ listResults && (<List>
|
||||
{listResults.map((listItem: string, listIndex: number) => (
|
||||
<li key={listIndex} title={listItem}>{snip(listItem)}</li>
|
||||
))}
|
||||
</List>)}
|
||||
</StyledRow>
|
||||
);
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ const MailConfigCard = (props: {data: any, title: string, actionButtons: any }):
|
||||
}
|
||||
{ mailServer.mailServices.length > 0 && <Heading as="h3" color={colors.primary} size="small">External Mail Services</Heading>}
|
||||
{ mailServer.mailServices && mailServer.mailServices.map((service: any, index: number) => (
|
||||
<Row lbl={service.provider} val={service.value} key={index} />
|
||||
<Row lbl={service.provider} title={service.value} val="" key={index} />
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ h4 {
|
||||
|
||||
const TechStackCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {
|
||||
const technologies = props.data.technologies;
|
||||
const iconsCdn = 'https://raw.githubusercontent.com/wappalyzer/wappalyzer/master/src/images/icons/';
|
||||
const iconsCdn = 'https://www.wappalyzer.com/images/icons/';
|
||||
return (
|
||||
<Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>
|
||||
{technologies.map((tech: any, index: number) => {
|
||||
|
@ -45,7 +45,7 @@ const MalwareCard = (props: {data: any, title: string, actionButtons: any }): JS
|
||||
<Row lbl="Threat Type" val={safeBrowsing?.details?.threatType || cloudmersive.WebsiteThreatType || 'None :)'} />
|
||||
)}
|
||||
{ phishTank && !phishTank.error && (
|
||||
<Row lbl="Phishing Status" val={phishTank.url0.in_database ? '❌ Phishing Identified' : '✅ No Phishing Identified!'} />
|
||||
<Row lbl="Phishing Status" val={phishTank?.url0?.in_database !== 'false' ? '❌ Phishing Identified' : '✅ No Phishing Found'} />
|
||||
)}
|
||||
{ phishTank.url0 && phishTank.url0.phish_detail_page && (
|
||||
<Row lbl="" val="">
|
||||
@ -53,10 +53,10 @@ const MalwareCard = (props: {data: any, title: string, actionButtons: any }): JS
|
||||
<span className="val"><a href={phishTank.url0.phish_detail_page}>{phishTank.url0.phish_id}</a></span>
|
||||
</Row>
|
||||
)}
|
||||
{ urlHaus.query_status === 'no_results' && <Row lbl="Status" val="✅ Nothing Found!" />}
|
||||
{ urlHaus.query_status === 'no_results' && <Row lbl="Malware Status" val="✅ No Malwares Found" />}
|
||||
{ urlHaus.query_status === 'ok' && (
|
||||
<>
|
||||
<Row lbl="Status" val="❌ Malware Found" />
|
||||
<Row lbl="Status" val="❌ Malware Identified" />
|
||||
<Row lbl="First Seen" val={convertToDate(urlHaus.firstseen)} />
|
||||
<Row lbl="Bad URLs Count" val={urlHaus.url_count} />
|
||||
</>
|
||||
|
66
src/components/Results/TlsCipherSuites.tsx
Normal file
66
src/components/Results/TlsCipherSuites.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Card } from 'components/Form/Card';
|
||||
import Button from 'components/Form/Button';
|
||||
import { ExpandableRow } from 'components/Form/Row';
|
||||
|
||||
const makeCipherSuites = (results: any) => {
|
||||
if (!results || !results.connection_info || (results.connection_info.ciphersuite || [])?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return results.connection_info.ciphersuite.map((ciphersuite: any) => {
|
||||
return {
|
||||
title: ciphersuite.cipher,
|
||||
fields: [
|
||||
{ lbl: 'Code', val: ciphersuite.code },
|
||||
{ lbl: 'Protocols', val: ciphersuite.protocols.join(', ') },
|
||||
{ lbl: 'Pubkey', val: ciphersuite.pubkey },
|
||||
{ lbl: 'Sigalg', val: ciphersuite.sigalg },
|
||||
{ lbl: 'Ticket Hint', val: ciphersuite.ticket_hint },
|
||||
{ lbl: 'OCSP Stapling', val: ciphersuite.ocsp_stapling ? '✅ Enabled' : '❌ Disabled' },
|
||||
{ lbl: 'PFS', val: ciphersuite.pfs },
|
||||
ciphersuite.curves ? { lbl: 'Curves', val: ciphersuite.curves.join(', ') } : {},
|
||||
]};
|
||||
});
|
||||
};
|
||||
|
||||
const TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {
|
||||
|
||||
const [cipherSuites, setCipherSuites] = useState(makeCipherSuites(props.data));
|
||||
const [loadState, setLoadState] = useState<undefined | 'loading' | 'success' | 'error'>(undefined);
|
||||
|
||||
const updateData = (id: number) => {
|
||||
setCipherSuites([]);
|
||||
setLoadState('loading');
|
||||
const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`;
|
||||
fetch(fetchUrl)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setCipherSuites(makeCipherSuites(data));
|
||||
setLoadState('success');
|
||||
}).catch((error) => {
|
||||
setLoadState('error');
|
||||
});
|
||||
};
|
||||
|
||||
const scanId = props.data?.id;
|
||||
return (
|
||||
<Card heading={props.title} actionButtons={props.actionButtons}>
|
||||
{ cipherSuites.length && cipherSuites.map((cipherSuite: any, index: number) => {
|
||||
return (
|
||||
<ExpandableRow lbl={cipherSuite.title} val="" rowList={cipherSuite.fields} />
|
||||
);
|
||||
})}
|
||||
{ !cipherSuites.length && (
|
||||
<div>
|
||||
<p>No cipher suites found.<br />
|
||||
This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it.
|
||||
</p>
|
||||
<Button loadState={loadState} onClick={() => updateData(scanId)}>Refetch Report</Button>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default TlsCard;
|
73
src/components/Results/TlsClientSupport.tsx
Normal file
73
src/components/Results/TlsClientSupport.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Card } from 'components/Form/Card';
|
||||
import Button from 'components/Form/Button';
|
||||
import { ExpandableRow } from 'components/Form/Row';
|
||||
|
||||
const makeClientSupport = (results: any) => {
|
||||
if (!results?.analysis) return [];
|
||||
const target = results.target;
|
||||
const sslLabsClientSupport = (
|
||||
results.analysis.find((a: any) => a.analyzer === 'sslLabsClientSupport')
|
||||
).result;
|
||||
|
||||
return sslLabsClientSupport.map((sup: any) => {
|
||||
return {
|
||||
title: `${sup.name} ${sup.platform ? `(on ${sup.platform})`: sup.version}`,
|
||||
value: sup.is_supported ? '✅' : '❌',
|
||||
fields: sup.is_supported ? [
|
||||
sup.curve ? { lbl: 'Curve', val: sup.curve } : {},
|
||||
{ lbl: 'Protocol', val: sup.protocol },
|
||||
{ lbl: 'Cipher Suite', val: sup.ciphersuite },
|
||||
{ lbl: 'Protocol Code', val: sup.protocol_code },
|
||||
{ lbl: 'Cipher Suite Code', val: sup.ciphersuite_code },
|
||||
{ lbl: 'Curve Code', val: sup.curve_code },
|
||||
] : [
|
||||
{ lbl: '', val: '',
|
||||
plaintext: `The host ${target} does not support ${sup.name} `
|
||||
+`${sup.version ? `version ${sup.version} `: ''} `
|
||||
+ `${sup.platform ? `on ${sup.platform} `: ''}`}
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {
|
||||
|
||||
const [clientSupport, setClientSupport] = useState(makeClientSupport(props.data));
|
||||
const [loadState, setLoadState] = useState<undefined | 'loading' | 'success' | 'error'>(undefined);
|
||||
|
||||
const updateData = (id: number) => {
|
||||
setClientSupport([]);
|
||||
setLoadState('loading');
|
||||
const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`;
|
||||
fetch(fetchUrl)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setClientSupport(makeClientSupport(data));
|
||||
setLoadState('success');
|
||||
}).catch(() => {
|
||||
setLoadState('error');
|
||||
});
|
||||
};
|
||||
|
||||
const scanId = props.data?.id;
|
||||
return (
|
||||
<Card heading={props.title} actionButtons={props.actionButtons}>
|
||||
{clientSupport.map((support: any) => {
|
||||
return (<ExpandableRow lbl={support.title} val={support.value || '?'} rowList={support.fields} />)
|
||||
})}
|
||||
{ !clientSupport.length && (
|
||||
<div>
|
||||
<p>No entries available to analyze.<br />
|
||||
This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it.
|
||||
</p>
|
||||
<Button loadState={loadState} onClick={() => updateData(scanId)}>Refetch Report</Button>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default TlsCard;
|
120
src/components/Results/TlsIssueAnalysis.tsx
Normal file
120
src/components/Results/TlsIssueAnalysis.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import colors from 'styles/colors';
|
||||
import { Card } from 'components/Form/Card';
|
||||
import Button from 'components/Form/Button';
|
||||
import Row, { ExpandableRow } from 'components/Form/Row';
|
||||
|
||||
const Expandable = styled.details`
|
||||
margin-top: 0.5rem;
|
||||
cursor: pointer;
|
||||
summary::marker {
|
||||
color: ${colors.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const makeExpandableData = (results: any) => {
|
||||
if (!results || !results.analysis || results.analysis.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return results.analysis.map((analysis: any) => {
|
||||
const fields = Object.keys(analysis.result).map((label) => {
|
||||
const lbl = isNaN(parseInt(label, 10)) ? label : '';
|
||||
const val = analysis.result[label] || 'None';
|
||||
if (typeof val !== 'object') {
|
||||
return { lbl, val };
|
||||
}
|
||||
return { lbl, val: '', plaintext: JSON.stringify(analysis.result[label])};
|
||||
});
|
||||
return {
|
||||
title: analysis.analyzer,
|
||||
value: analysis.success ? '✅' : '❌',
|
||||
fields,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const makeResults = (results: any) => {
|
||||
const rows: { lbl: string; val?: any; plaintext?: string; list?: string[] }[] = [];
|
||||
if (!results || !results.analysis || results.analysis.length === 0) {
|
||||
return rows;
|
||||
}
|
||||
const caaWorker = results.analysis.find((a: any) => a.analyzer === 'caaWorker');
|
||||
if (caaWorker.result.host) rows.push({ lbl: 'Host', val: caaWorker.result.host });
|
||||
if (typeof caaWorker.result.has_caa === 'boolean') rows.push({ lbl: 'CA Authorization', val: caaWorker.result.has_caa });
|
||||
if (caaWorker.result.issue) rows.push({ lbl: 'CAAs allowed to Issue Certs', plaintext: caaWorker.result.issue.join('\n') });
|
||||
|
||||
const mozillaGradingWorker = (results.analysis.find((a: any) => a.analyzer === 'mozillaGradingWorker')).result;
|
||||
if (mozillaGradingWorker.grade) rows.push({ lbl: 'Mozilla Grading', val: mozillaGradingWorker.grade });
|
||||
if (mozillaGradingWorker.gradeTrust) rows.push({ lbl: 'Mozilla Trust', val: mozillaGradingWorker.gradeTrust });
|
||||
|
||||
const symantecDistrust = (results.analysis.find((a: any) => a.analyzer === 'symantecDistrust')).result;
|
||||
if (typeof symantecDistrust.isDistrusted === 'boolean') rows.push({ lbl: 'No distrusted symantec SSL?', val: !symantecDistrust.isDistrusted });
|
||||
if (symantecDistrust.reasons) rows.push({ lbl: 'Symantec Distrust', plaintext: symantecDistrust.reasons.join('\n') });
|
||||
|
||||
const top1m = (results.analysis.find((a: any) => a.analyzer === 'top1m')).result;
|
||||
if (top1m.certificate.rank) rows.push({ lbl: 'Certificate Rank', val: top1m.certificate.rank.toLocaleString() });
|
||||
|
||||
const mozillaEvaluationWorker = (results.analysis.find((a: any) => a.analyzer === 'mozillaEvaluationWorker')).result;
|
||||
if (mozillaEvaluationWorker.level) rows.push({ lbl: 'Mozilla Evaluation Level', val: mozillaEvaluationWorker.level });
|
||||
if (mozillaEvaluationWorker.failures) {
|
||||
const { bad, old, intermediate, modern } = mozillaEvaluationWorker.failures;
|
||||
if (bad) rows.push({ lbl: `Critical Security Issues (${bad.length})`, list: bad });
|
||||
if (old) rows.push({ lbl: `Compatibility Config Issues (${old.length})`, list: old });
|
||||
if (intermediate) rows.push({ lbl: `Intermediate Issues (${intermediate.length})`, list: intermediate });
|
||||
if (modern) rows.push({ lbl: `Modern Issues (${modern.length})`, list: modern });
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
const TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {
|
||||
|
||||
const [tlsRowData, setTlsRowWata] = useState(makeExpandableData(props.data));
|
||||
const [tlsResults, setTlsResults] = useState(makeResults(props.data));
|
||||
const [loadState, setLoadState] = useState<undefined | 'loading' | 'success' | 'error'>(undefined);
|
||||
|
||||
const updateData = (id: number) => {
|
||||
setTlsRowWata([]);
|
||||
setLoadState('loading');
|
||||
const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`;
|
||||
fetch(fetchUrl)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setTlsRowWata(makeExpandableData(data));
|
||||
setTlsResults(makeResults(data));
|
||||
setLoadState('success');
|
||||
}).catch(() => {
|
||||
setLoadState('error');
|
||||
});
|
||||
};
|
||||
|
||||
const scanId = props.data?.id;
|
||||
return (
|
||||
<Card heading={props.title} actionButtons={props.actionButtons}>
|
||||
{ tlsResults.length > 0 && tlsResults.map((row: any, index: number) => {
|
||||
return (
|
||||
<Row lbl={row.lbl} val={row.val} plaintext={row.plaintext} listResults={row.list} />
|
||||
);
|
||||
})}
|
||||
<Expandable>
|
||||
<summary>Full Analysis Results</summary>
|
||||
{ tlsRowData.length > 0 && tlsRowData.map((cipherSuite: any, index: number) => {
|
||||
return (
|
||||
<ExpandableRow lbl={cipherSuite.title} val={cipherSuite.value || '?'} rowList={cipherSuite.fields} />
|
||||
);
|
||||
})}
|
||||
</Expandable>
|
||||
{ !tlsRowData.length && (
|
||||
<div>
|
||||
<p>No entries available to analyze.<br />
|
||||
This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it.
|
||||
</p>
|
||||
<Button loadState={loadState} onClick={() => updateData(scanId)}>Refetch Report</Button>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default TlsCard;
|
@ -16,6 +16,12 @@ const LoaderContainer = styled(StyledCard)`
|
||||
gap: 2rem;
|
||||
height: 50vh;
|
||||
transition: all 0.3s ease-in-out;
|
||||
p.loadTimeInfo {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
color: ${colors.textColorSecondary};
|
||||
opacity: 0.5;
|
||||
}
|
||||
&.flex {
|
||||
display: flex;
|
||||
}
|
||||
@ -46,7 +52,7 @@ const StyledSvg = styled.svg`
|
||||
const Loader = (props: { show: boolean }): JSX.Element => {
|
||||
return (
|
||||
<LoaderContainer className={props.show ? '' : 'finished'}>
|
||||
<Heading as="h4" color={colors.primary}>Fetching data...</Heading>
|
||||
<Heading as="h4" color={colors.primary}>Crunching data...</Heading>
|
||||
<StyledSvg version="1.1" id="L7" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 100 100" enableBackground="new 0 0 100 100">
|
||||
<path fill="#fff" d="M31.6,3.5C5.9,13.6-6.6,42.7,3.5,68.4c10.1,25.7,39.2,38.3,64.9,28.1l-3.1-7.9c-21.3,8.4-45.4-2-53.8-23.3
|
||||
@ -83,6 +89,10 @@ const Loader = (props: { show: boolean }): JSX.Element => {
|
||||
repeatCount="indefinite" />
|
||||
</path>
|
||||
</StyledSvg>
|
||||
<p className="loadTimeInfo">
|
||||
It may take up-to a minute for all jobs to complete<br />
|
||||
You can view preliminary results as they come in below
|
||||
</p>
|
||||
</LoaderContainer>
|
||||
);
|
||||
}
|
||||
|
@ -186,39 +186,42 @@ export interface LoadingJob {
|
||||
const jobNames = [
|
||||
'get-ip',
|
||||
'location',
|
||||
'headers',
|
||||
'domain',
|
||||
'dns',
|
||||
'dns-server',
|
||||
'tech-stack',
|
||||
'hosts',
|
||||
'quality',
|
||||
'cookies',
|
||||
'ssl',
|
||||
// 'server-info',
|
||||
'redirects',
|
||||
'robots-txt',
|
||||
'domain',
|
||||
'quality',
|
||||
'tech-stack',
|
||||
'server-info',
|
||||
'cookies',
|
||||
'headers',
|
||||
'dns',
|
||||
'hosts',
|
||||
'http-security',
|
||||
'social-tags',
|
||||
'trace-route',
|
||||
'security-txt',
|
||||
'dns-server',
|
||||
'firewall',
|
||||
'dnssec',
|
||||
'hsts',
|
||||
'threats',
|
||||
'mail-config',
|
||||
'archives',
|
||||
'rank',
|
||||
'screenshot',
|
||||
'tls-cipher-suites',
|
||||
'tls-security-config',
|
||||
'tls-client-support',
|
||||
'redirects',
|
||||
'linked-pages',
|
||||
'robots-txt',
|
||||
'status',
|
||||
'ports',
|
||||
'screenshot',
|
||||
'txt-records',
|
||||
'sitemap',
|
||||
'hsts',
|
||||
'security-txt',
|
||||
'social-tags',
|
||||
'linked-pages',
|
||||
'mail-config',
|
||||
// 'whois',
|
||||
'features',
|
||||
'carbon',
|
||||
'trace-route',
|
||||
'firewall',
|
||||
'http-security',
|
||||
'rank',
|
||||
'archives',
|
||||
'txt-records',
|
||||
'block-lists',
|
||||
'threats',
|
||||
'features',
|
||||
'sitemap',
|
||||
'carbon',
|
||||
] as const;
|
||||
|
||||
export const initialJobs = jobNames.map((job: string) => {
|
||||
|
@ -7,11 +7,11 @@ import { AddressType } from 'utils/address-type-checker';
|
||||
|
||||
interface UseIpAddressProps<ResultType = any> {
|
||||
// Unique identifier for this job type
|
||||
jobId: string;
|
||||
jobId: string | string[];
|
||||
// The actual fetch request
|
||||
fetchRequest: () => Promise<ResultType>;
|
||||
// Function to call to update the loading state in parent
|
||||
updateLoadingJobs: (job: string, newState: LoadingState, error?: string, retry?: (data?: any) => void | null, data?: any) => void;
|
||||
updateLoadingJobs: (job: string | string[], newState: LoadingState, error?: string, retry?: (data?: any) => void | null, data?: any) => void;
|
||||
addressInfo: {
|
||||
// The hostname/ip address that we're checking
|
||||
address: string | undefined;
|
||||
|
@ -7,7 +7,7 @@ import Nav from 'components/Form/Nav';
|
||||
import Button from 'components/Form/Button';
|
||||
import AdditionalResources from 'components/misc/AdditionalResources';
|
||||
import { StyledCard } from 'components/Form/Card';
|
||||
import docs, { about, license, fairUse, supportUs } from 'utils/docs';
|
||||
import docs, { about, featureIntro, license, fairUse, supportUs } from 'utils/docs';
|
||||
|
||||
const AboutContainer = styled.div`
|
||||
width: 95vw;
|
||||
@ -120,10 +120,20 @@ const About = (): JSX.Element => {
|
||||
{about.map((para, index: number) => (
|
||||
<p key={index}>{para}</p>
|
||||
))}
|
||||
<hr />
|
||||
<p>
|
||||
Web-Check is developed and maintained by <a href="https://aliciasykes.com">Alicia Sykes</a>.
|
||||
It's licensed under the <a href="https://github.com/Lissy93/web-check/blob/master/LICENSE">MIT license</a>,
|
||||
and is completely free to use, modify and distribute in both personal and commercial settings.<br />
|
||||
Source code and self-hosting docs are available on <a href="https://github.com/lissy93/web-check">GitHub</a>.
|
||||
If you've found this service useful, consider <a href="https://github.com/sponsors/Lissy93">sponsoring me</a> from $1/month,
|
||||
to help with the ongoing hosting and development costs.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Heading as="h2" size="medium" color={colors.primary}>Features</Heading>
|
||||
<Section>
|
||||
{featureIntro.map((fi: string, i: number) => (<p key={i}>{fi}</p>))}
|
||||
<div className="contents">
|
||||
<Heading as="h3" size="small" id="#feature-contents" color={colors.primary}>Contents</Heading>
|
||||
<ul>
|
||||
@ -165,7 +175,6 @@ const About = (): JSX.Element => {
|
||||
))}
|
||||
</ul>
|
||||
</>}
|
||||
{/* { sectionIndex < docs.length - 1 && <hr /> } */}
|
||||
</section>
|
||||
))}
|
||||
</Section>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback, ReactNode } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import Masonry from 'react-masonry-css'
|
||||
@ -54,6 +54,9 @@ import ArchivesCard from 'components/Results/Archives';
|
||||
import RankCard from 'components/Results/Rank';
|
||||
import BlockListsCard from 'components/Results/BlockLists';
|
||||
import ThreatsCard from 'components/Results/Threats';
|
||||
import TlsCipherSuitesCard from 'components/Results/TlsCipherSuites';
|
||||
import TlsIssueAnalysisCard from 'components/Results/TlsIssueAnalysis';
|
||||
import TlsClientSupportCard from 'components/Results/TlsClientSupport';
|
||||
|
||||
import keys from 'utils/get-keys';
|
||||
import { determineAddressType, AddressType } from 'utils/address-type-checker';
|
||||
@ -86,6 +89,68 @@ const ResultsContent = styled.section`
|
||||
padding-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const FilterButtons = styled.div`
|
||||
width: 95vw;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
.one-half {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
button, input, .toggle-filters {
|
||||
background: ${colors.backgroundLighter};
|
||||
color: ${colors.textColor};
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-family: 'PTMono';
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
button, .toggle-filters {
|
||||
cursor: pointer;
|
||||
text-transform: capitalize;
|
||||
box-shadow: 2px 2px 0px ${colors.bgShadowColor};
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
box-shadow: 4px 4px 0px ${colors.bgShadowColor};
|
||||
color: ${colors.primary};
|
||||
}
|
||||
&.selected {
|
||||
border: 1px solid ${colors.primary};
|
||||
color: ${colors.primary};
|
||||
}
|
||||
}
|
||||
input:focus {
|
||||
border: 1px solid ${colors.primary};
|
||||
outline: none;
|
||||
}
|
||||
.clear {
|
||||
color: ${colors.textColor};
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.toggle-filters {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.control-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Results = (): JSX.Element => {
|
||||
const startTime = new Date().getTime();
|
||||
|
||||
@ -95,13 +160,27 @@ const Results = (): JSX.Element => {
|
||||
const [loadingJobs, setLoadingJobs] = useState<LoadingJob[]>(initialJobs);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [modalContent, setModalContent] = useState<ReactNode>(<></>);
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
|
||||
const updateLoadingJobs = useCallback((job: string, newState: LoadingState, error?: string, retry?: () => void, data?: any) => {
|
||||
const clearFilters = () => {
|
||||
setTags([]);
|
||||
setSearchTerm('');
|
||||
};
|
||||
const updateTags = (tag: string) => {
|
||||
// Remove current tag if it exists, otherwise add it
|
||||
// setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [...tags, tag]);
|
||||
setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [tag]);
|
||||
};
|
||||
|
||||
const updateLoadingJobs = useCallback((jobs: string | string[], newState: LoadingState, error?: string, retry?: () => void, data?: any) => {
|
||||
(typeof jobs === 'string' ? [jobs] : jobs).forEach((job: string) => {
|
||||
const now = new Date();
|
||||
const timeTaken = now.getTime() - startTime;
|
||||
setLoadingJobs((prevJobs) => {
|
||||
const newJobs = prevJobs.map((loadingJob: LoadingJob) => {
|
||||
if (loadingJob.name === job) {
|
||||
if (job.includes(loadingJob.name)) {
|
||||
return { ...loadingJob, error, state: newState, timeTaken, retry };
|
||||
}
|
||||
return loadingJob;
|
||||
@ -137,6 +216,7 @@ const Results = (): JSX.Element => {
|
||||
}
|
||||
return newJobs;
|
||||
});
|
||||
});
|
||||
}, [startTime]);
|
||||
|
||||
const parseJson = (response: Response): Promise<any> => {
|
||||
@ -175,6 +255,16 @@ const Results = (): JSX.Element => {
|
||||
}
|
||||
}, [address, addressType, setIpAddress]);
|
||||
|
||||
// Get IP address location info
|
||||
const [locationResults, updateLocationResults] = useMotherHook<ServerLocation>({
|
||||
jobId: 'location',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
|
||||
fetchRequest: () => fetch(`https://ipapi.co/${ipAddress}/json/`)
|
||||
.then(res => parseJson(res))
|
||||
.then(res => getLocation(res)),
|
||||
});
|
||||
|
||||
// Fetch and parse SSL certificate info
|
||||
const [sslResults, updateSslResults] = useMotherHook({
|
||||
jobId: 'ssl',
|
||||
@ -183,6 +273,42 @@ const Results = (): JSX.Element => {
|
||||
fetchRequest: () => fetch(`${api}/ssl?url=${address}`).then((res) => parseJson(res)),
|
||||
});
|
||||
|
||||
// Run a manual whois lookup on the domain
|
||||
const [domainLookupResults, updateDomainLookupResults] = useMotherHook({
|
||||
jobId: 'domain',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/whois?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetch and parse Lighthouse performance data
|
||||
const [lighthouseResults, updateLighthouseResults] = useMotherHook({
|
||||
jobId: 'quality',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/quality?url=${address}`)
|
||||
.then(res => parseJson(res))
|
||||
.then(res => res?.lighthouseResult || { error: 'No Data'}),
|
||||
});
|
||||
|
||||
// Get the technologies used to build site, using Wappalyzer
|
||||
const [techStackResults, updateTechStackResults] = useMotherHook({
|
||||
jobId: 'tech-stack',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/tech-stack?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get hostnames and associated domains from Shodan
|
||||
const [shoadnResults, updateShodanResults] = useMotherHook<ShodanResults>({
|
||||
jobId: ['hosts', 'server-info'],
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
|
||||
fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`)
|
||||
.then(res => parseJson(res))
|
||||
.then(res => parseShodanResults(res)),
|
||||
});
|
||||
|
||||
// Fetch and parse cookies info
|
||||
const [cookieResults, updateCookieResults] = useMotherHook<{cookies: Cookie[]}>({
|
||||
jobId: 'cookies',
|
||||
@ -192,15 +318,6 @@ const Results = (): JSX.Element => {
|
||||
.then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetch and parse crawl rules from robots.txt
|
||||
const [robotsTxtResults, updateRobotsTxtResults] = useMotherHook<{robots: RowProps[]}>({
|
||||
jobId: 'robots-txt',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/robots-txt?url=${address}`)
|
||||
.then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetch and parse headers
|
||||
const [headersResults, updateHeadersResults] = useMotherHook({
|
||||
jobId: 'headers',
|
||||
@ -217,34 +334,149 @@ const Results = (): JSX.Element => {
|
||||
fetchRequest: () => fetch(`${api}/dns?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetch and parse Lighthouse performance data
|
||||
const [lighthouseResults, updateLighthouseResults] = useMotherHook({
|
||||
jobId: 'quality',
|
||||
// Get HTTP security
|
||||
const [httpSecurityResults, updateHttpSecurityResults] = useMotherHook({
|
||||
jobId: 'http-security',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/quality?url=${address}`)
|
||||
.then(res => parseJson(res))
|
||||
.then(res => res?.lighthouseResult || { error: 'No Data'}),
|
||||
fetchRequest: () => fetch(`${api}/http-security?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get IP address location info
|
||||
const [locationResults, updateLocationResults] = useMotherHook<ServerLocation>({
|
||||
jobId: 'location',
|
||||
// Get social media previews, from a sites social meta tags
|
||||
const [socialTagResults, updateSocialTagResults] = useMotherHook({
|
||||
jobId: 'social-tags',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
|
||||
fetchRequest: () => fetch(`https://ipapi.co/${ipAddress}/json/`)
|
||||
.then(res => parseJson(res))
|
||||
.then(res => getLocation(res)),
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/social-tags?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get hostnames and associated domains from Shodan
|
||||
const [shoadnResults, updateShodanResults] = useMotherHook<ShodanResults>({
|
||||
jobId: 'hosts',
|
||||
// Get trace route for a given hostname
|
||||
const [traceRouteResults, updateTraceRouteResults] = useMotherHook({
|
||||
jobId: 'trace-route',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
|
||||
fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`)
|
||||
.then(res => parseJson(res))
|
||||
.then(res => parseShodanResults(res)),
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/trace-route?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get a websites listed pages, from sitemap
|
||||
const [securityTxtResults, updateSecurityTxtResults] = useMotherHook({
|
||||
jobId: 'security-txt',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get the DNS server(s) for a domain, and test DoH/DoT support
|
||||
const [dnsServerResults, updateDnsServerResults] = useMotherHook({
|
||||
jobId: 'dns-server',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/dns-server?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get the WAF and Firewall info for a site
|
||||
const [firewallResults, updateFirewallResults] = useMotherHook({
|
||||
jobId: 'firewall',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/firewall?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get DNSSEC info
|
||||
const [dnsSecResults, updateDnsSecResults] = useMotherHook({
|
||||
jobId: 'dnssec',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/dnssec?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Check if a site is on the HSTS preload list
|
||||
const [hstsResults, updateHstsResults] = useMotherHook({
|
||||
jobId: 'hsts',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/hsts?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Check if a host is present on the URLHaus malware list
|
||||
const [threatResults, updateThreatResults] = useMotherHook({
|
||||
jobId: 'threats',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/threats?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get mail config for server, based on DNS records
|
||||
const [mailConfigResults, updateMailConfigResults] = useMotherHook({
|
||||
jobId: 'mail-config',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/mail-config?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get list of archives from the Wayback Machine
|
||||
const [archivesResults, updateArchivesResults] = useMotherHook({
|
||||
jobId: 'archives',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/archives?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get website's global ranking, from Tranco
|
||||
const [rankResults, updateRankResults] = useMotherHook({
|
||||
jobId: 'rank',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/rank?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Take a screenshot of the website
|
||||
const [screenshotResult, updateScreenshotResult] = useMotherHook({
|
||||
jobId: 'screenshot',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/screenshot?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get TLS security info, from Mozilla Observatory
|
||||
const [tlsResults, updateTlsResults] = useMotherHook({
|
||||
jobId: ['tls-cipher-suites', 'tls-security-config', 'tls-client-support'],
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/tls?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetches URL redirects
|
||||
const [redirectResults, updateRedirectResults] = useMotherHook({
|
||||
jobId: 'redirects',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/redirects?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get list of links included in the page content
|
||||
const [linkedPagesResults, updateLinkedPagesResults] = useMotherHook({
|
||||
jobId: 'linked-pages',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/linked-pages?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetch and parse crawl rules from robots.txt
|
||||
const [robotsTxtResults, updateRobotsTxtResults] = useMotherHook<{robots: RowProps[]}>({
|
||||
jobId: 'robots-txt',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/robots-txt?url=${address}`)
|
||||
.then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get current status and response time of server
|
||||
const [serverStatusResults, updateServerStatusResults] = useMotherHook({
|
||||
jobId: 'status',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/status?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Check for open ports
|
||||
@ -274,52 +506,12 @@ const Results = (): JSX.Element => {
|
||||
fetchRequest: () => fetch(`${api}/txt-records?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetches URL redirects
|
||||
const [redirectResults, updateRedirectResults] = useMotherHook({
|
||||
jobId: 'redirects',
|
||||
// Check site against DNS blocklists
|
||||
const [blockListsResults, updateBlockListsResults] = useMotherHook({
|
||||
jobId: 'block-lists',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/redirects?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get current status and response time of server
|
||||
const [serverStatusResults, updateServerStatusResults] = useMotherHook({
|
||||
jobId: 'status',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/status?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get current status and response time of server
|
||||
const [techStackResults, updateTechStackResults] = useMotherHook({
|
||||
jobId: 'tech-stack',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/tech-stack?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get trace route for a given hostname
|
||||
const [traceRouteResults, updateTraceRouteResults] = useMotherHook({
|
||||
jobId: 'trace-route',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/trace-route?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Fetch carbon footprint data for a given site
|
||||
const [carbonResults, updateCarbonResults] = useMotherHook({
|
||||
jobId: 'carbon',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/carbon?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Check if a site is on the HSTS preload list
|
||||
const [hstsResults, updateHstsResults] = useMotherHook({
|
||||
jobId: 'hsts',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/hsts?url=${address}`).then(res => parseJson(res)),
|
||||
fetchRequest: () => fetch(`${api}/block-lists?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get a websites listed pages, from sitemap
|
||||
@ -330,28 +522,12 @@ const Results = (): JSX.Element => {
|
||||
fetchRequest: () => fetch(`${api}/sitemap?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get a websites listed pages, from sitemap
|
||||
const [screenshotResult, updateScreenshotResult] = useMotherHook({
|
||||
jobId: 'screenshot',
|
||||
// Fetch carbon footprint data for a given site
|
||||
const [carbonResults, updateCarbonResults] = useMotherHook({
|
||||
jobId: 'carbon',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/screenshot?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get a websites listed pages, from sitemap
|
||||
const [securityTxtResults, updateSecurityTxtResults] = useMotherHook({
|
||||
jobId: 'security-txt',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get social media previews, from a sites social meta tags
|
||||
const [socialTagResults, updateSocialTagResults] = useMotherHook({
|
||||
jobId: 'social-tags',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/social-tags?url=${address}`).then(res => parseJson(res)),
|
||||
fetchRequest: () => fetch(`${api}/carbon?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get site features from BuiltWith
|
||||
@ -369,94 +545,6 @@ const Results = (): JSX.Element => {
|
||||
}),
|
||||
});
|
||||
|
||||
// Get DNSSEC info
|
||||
const [dnsSecResults, updateDnsSecResults] = useMotherHook({
|
||||
jobId: 'dnssec',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/dnssec?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Run a manual whois lookup on the domain
|
||||
const [domainLookupResults, updateDomainLookupResults] = useMotherHook({
|
||||
jobId: 'domain',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/whois?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get the DNS server(s) for a domain, and test DoH/DoT support
|
||||
const [dnsServerResults, updateDnsServerResults] = useMotherHook({
|
||||
jobId: 'dns-server',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/dns-server?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get list of links included in the page content
|
||||
const [linkedPagesResults, updateLinkedPagesResults] = useMotherHook({
|
||||
jobId: 'linked-pages',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/linked-pages?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get mail config for server, based on DNS records
|
||||
const [mailConfigResults, updateMailConfigResults] = useMotherHook({
|
||||
jobId: 'mail-config',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/mail-config?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get the WAF and Firewall info for a site
|
||||
const [firewallResults, updateFirewallResults] = useMotherHook({
|
||||
jobId: 'firewall',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/firewall?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get the WAF and Firewall info for a site
|
||||
const [httpSecurityResults, updateHttpSecurityResults] = useMotherHook({
|
||||
jobId: 'firewall',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/http-security?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get list of archives from the Wayback Machine
|
||||
const [archivesResults, updateArchivesResults] = useMotherHook({
|
||||
jobId: 'archives',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/archives?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Get website's global ranking, from Tranco
|
||||
const [rankResults, updateRankResults] = useMotherHook({
|
||||
jobId: 'rank',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/rank?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Check site against DNS blocklists
|
||||
const [blockListsResults, updateBlockListsResults] = useMotherHook({
|
||||
jobId: 'block-lists',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/block-lists?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
// Check if a host is present on the URLHaus malware list
|
||||
const [threatResults, updateThreatResults] = useMotherHook({
|
||||
jobId: 'threats',
|
||||
updateLoadingJobs,
|
||||
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
|
||||
fetchRequest: () => fetch(`${api}/threats?url=${address}`).then(res => parseJson(res)),
|
||||
});
|
||||
|
||||
/* Cancel remaining jobs after 10 second timeout */
|
||||
useEffect(() => {
|
||||
const checkJobs = () => {
|
||||
@ -470,7 +558,7 @@ const Results = (): JSX.Element => {
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [loadingJobs, updateLoadingJobs]); // dependencies for the effect
|
||||
}, [loadingJobs, updateLoadingJobs]);
|
||||
|
||||
const makeSiteName = (address: string): string => {
|
||||
try {
|
||||
@ -482,44 +570,269 @@ const Results = (): JSX.Element => {
|
||||
|
||||
// A list of state sata, corresponding component and title for each card
|
||||
const resultCardData = [
|
||||
{ id: 'location', title: 'Server Location', result: locationResults, Component: ServerLocationCard, refresh: updateLocationResults },
|
||||
{ id: 'ssl', title: 'SSL Info', result: sslResults, Component: SslCertCard, refresh: updateSslResults },
|
||||
{ id: 'headers', title: 'Headers', result: headersResults, Component: HeadersCard, refresh: updateHeadersResults },
|
||||
{ id: 'domain', title: 'Whois', result: domainLookupResults, Component: DomainLookup, refresh: updateDomainLookupResults },
|
||||
{ id: 'dns', title: 'DNS Records', result: dnsResults, Component: DnsRecordsCard, refresh: updateDnsResults },
|
||||
{ id: 'hosts', title: 'Host Names', result: shoadnResults?.hostnames, Component: HostNamesCard, refresh: updateShodanResults },
|
||||
{ id: 'http-security', title: 'HTTP Security', result: httpSecurityResults, Component: HttpSecurityCard, refresh: updateHttpSecurityResults },
|
||||
{ id: 'tech-stack', title: 'Tech Stack', result: techStackResults, Component: TechStackCard, refresh: updateTechStackResults },
|
||||
{ id: 'quality', title: 'Quality Summary', result: lighthouseResults, Component: LighthouseCard, refresh: updateLighthouseResults },
|
||||
{ id: 'social-tags', title: 'Social Tags', result: socialTagResults, Component: SocialTagsCard, refresh: updateSocialTagResults },
|
||||
{ id: 'cookies', title: 'Cookies', result: cookieResults, Component: CookiesCard, refresh: updateCookieResults },
|
||||
{ id: 'trace-route', title: 'Trace Route', result: traceRouteResults, Component: TraceRouteCard, refresh: updateTraceRouteResults },
|
||||
{ id: 'firewall', title: 'Firewall', result: firewallResults, Component: FirewallCard, refresh: updateFirewallResults },
|
||||
{ id: 'server-info', title: 'Server Info', result: shoadnResults?.serverInfo, Component: ServerInfoCard, refresh: updateShodanResults },
|
||||
{ id: 'redirects', title: 'Redirects', result: redirectResults, Component: RedirectsCard, refresh: updateRedirectResults },
|
||||
{ id: 'robots-txt', title: 'Crawl Rules', result: robotsTxtResults, Component: RobotsTxtCard, refresh: updateRobotsTxtResults },
|
||||
{ id: 'dnssec', title: 'DNSSEC', result: dnsSecResults, Component: DnsSecCard, refresh: updateDnsSecResults },
|
||||
{ id: 'status', title: 'Server Status', result: serverStatusResults, Component: ServerStatusCard, refresh: updateServerStatusResults },
|
||||
{ id: 'ports', title: 'Open Ports', result: portsResults, Component: OpenPortsCard, refresh: updatePortsResults },
|
||||
{ id: 'security-txt', title: 'Security.Txt', result: securityTxtResults, Component: SecurityTxtCard, refresh: updateSecurityTxtResults },
|
||||
{ id: 'rank', title: 'Global Ranking', result: rankResults, Component: RankCard, refresh: updateRankResults },
|
||||
{ id: 'screenshot', title: 'Screenshot', result: screenshotResult || lighthouseResults?.fullPageScreenshot?.screenshot, Component: ScreenshotCard, refresh: updateScreenshotResult },
|
||||
{ id: 'mail-config', title: 'Email Configuration', result: mailConfigResults, Component: MailConfigCard, refresh: updateMailConfigResults },
|
||||
{ id: 'hsts', title: 'HSTS Check', result: hstsResults, Component: HstsCard, refresh: updateHstsResults },
|
||||
{ id: 'archives', title: 'Archive History', result: archivesResults, Component: ArchivesCard, refresh: updateArchivesResults },
|
||||
{ id: 'whois', title: 'Domain Info', result: whoIsResults, Component: WhoIsCard, refresh: updateWhoIsResults },
|
||||
{ id: 'dns-server', title: 'DNS Server', result: dnsServerResults, Component: DnsServerCard, refresh: updateDnsServerResults },
|
||||
{ id: 'linked-pages', title: 'Linked Pages', result: linkedPagesResults, Component: ContentLinksCard, refresh: updateLinkedPagesResults },
|
||||
{ id: 'txt-records', title: 'TXT Records', result: txtRecordResults, Component: TxtRecordCard, refresh: updateTxtRecordResults },
|
||||
{ id: 'block-lists', title: 'Block Lists', result: blockListsResults, Component: BlockListsCard, refresh: updateBlockListsResults },
|
||||
{ id: 'threats', title: 'Threats', result: threatResults, Component: ThreatsCard, refresh: updateThreatResults },
|
||||
{ id: 'features', title: 'Site Features', result: siteFeaturesResults, Component: SiteFeaturesCard, refresh: updateSiteFeaturesResults },
|
||||
{ id: 'sitemap', title: 'Pages', result: sitemapResults, Component: SitemapCard, refresh: updateSitemapResults },
|
||||
{ id: 'carbon', title: 'Carbon Footprint', result: carbonResults, Component: CarbonFootprintCard, refresh: updateCarbonResults },
|
||||
|
||||
{
|
||||
id: 'location',
|
||||
title: 'Server Location',
|
||||
result: locationResults,
|
||||
Component: ServerLocationCard,
|
||||
refresh: updateLocationResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'ssl',
|
||||
title: 'SSL Certificate',
|
||||
result: sslResults,
|
||||
Component: SslCertCard,
|
||||
refresh: updateSslResults,
|
||||
tags: ['server', 'security'],
|
||||
}, {
|
||||
id: 'domain',
|
||||
title: 'Domain Whois',
|
||||
result: domainLookupResults,
|
||||
Component: DomainLookup,
|
||||
refresh: updateDomainLookupResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'quality',
|
||||
title: 'Quality Summary',
|
||||
result: lighthouseResults,
|
||||
Component: LighthouseCard,
|
||||
refresh: updateLighthouseResults,
|
||||
tags: ['client'],
|
||||
}, {
|
||||
id: 'tech-stack',
|
||||
title: 'Tech Stack',
|
||||
result: techStackResults,
|
||||
Component: TechStackCard,
|
||||
refresh: updateTechStackResults,
|
||||
tags: ['client', 'meta'],
|
||||
}, {
|
||||
id: 'server-info',
|
||||
title: 'Server Info',
|
||||
result: shoadnResults?.serverInfo,
|
||||
Component: ServerInfoCard,
|
||||
refresh: updateShodanResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'cookies',
|
||||
title: 'Cookies',
|
||||
result: cookieResults,
|
||||
Component: CookiesCard,
|
||||
refresh: updateCookieResults,
|
||||
tags: ['client', 'security'],
|
||||
}, {
|
||||
id: 'headers',
|
||||
title: 'Headers',
|
||||
result: headersResults,
|
||||
Component: HeadersCard,
|
||||
refresh: updateHeadersResults,
|
||||
tags: ['client', 'security'],
|
||||
}, {
|
||||
id: 'dns',
|
||||
title: 'DNS Records',
|
||||
result: dnsResults,
|
||||
Component: DnsRecordsCard,
|
||||
refresh: updateDnsResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'hosts',
|
||||
title: 'Host Names',
|
||||
result: shoadnResults?.hostnames,
|
||||
Component: HostNamesCard,
|
||||
refresh: updateShodanResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'http-security',
|
||||
title: 'HTTP Security',
|
||||
result: httpSecurityResults,
|
||||
Component: HttpSecurityCard,
|
||||
refresh: updateHttpSecurityResults,
|
||||
tags: ['security'],
|
||||
}, {
|
||||
id: 'social-tags',
|
||||
title: 'Social Tags',
|
||||
result: socialTagResults,
|
||||
Component: SocialTagsCard,
|
||||
refresh: updateSocialTagResults,
|
||||
tags: ['client', 'meta'],
|
||||
}, {
|
||||
id: 'trace-route',
|
||||
title: 'Trace Route',
|
||||
result: traceRouteResults,
|
||||
Component: TraceRouteCard,
|
||||
refresh: updateTraceRouteResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'security-txt',
|
||||
title: 'Security.Txt',
|
||||
result: securityTxtResults,
|
||||
Component: SecurityTxtCard,
|
||||
refresh: updateSecurityTxtResults,
|
||||
tags: ['security'],
|
||||
}, {
|
||||
id: 'dns-server',
|
||||
title: 'DNS Server',
|
||||
result: dnsServerResults,
|
||||
Component: DnsServerCard,
|
||||
refresh: updateDnsServerResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'firewall',
|
||||
title: 'Firewall',
|
||||
result: firewallResults,
|
||||
Component: FirewallCard,
|
||||
refresh: updateFirewallResults,
|
||||
tags: ['server', 'security'],
|
||||
}, {
|
||||
id: 'dnssec',
|
||||
title: 'DNSSEC',
|
||||
result: dnsSecResults,
|
||||
Component: DnsSecCard,
|
||||
refresh: updateDnsSecResults,
|
||||
tags: ['security'],
|
||||
}, {
|
||||
id: 'hsts',
|
||||
title: 'HSTS Check',
|
||||
result: hstsResults,
|
||||
Component: HstsCard,
|
||||
refresh: updateHstsResults,
|
||||
tags: ['security'],
|
||||
}, {
|
||||
id: 'threats',
|
||||
title: 'Threats',
|
||||
result: threatResults,
|
||||
Component: ThreatsCard,
|
||||
refresh: updateThreatResults,
|
||||
tags: ['security'],
|
||||
}, {
|
||||
id: 'mail-config',
|
||||
title: 'Email Configuration',
|
||||
result: mailConfigResults,
|
||||
Component: MailConfigCard,
|
||||
refresh: updateMailConfigResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'archives',
|
||||
title: 'Archive History',
|
||||
result: archivesResults,
|
||||
Component: ArchivesCard,
|
||||
refresh: updateArchivesResults,
|
||||
tags: ['meta'],
|
||||
}, {
|
||||
id: 'rank',
|
||||
title: 'Global Ranking',
|
||||
result: rankResults,
|
||||
Component: RankCard,
|
||||
refresh: updateRankResults,
|
||||
tags: ['meta'],
|
||||
}, {
|
||||
id: 'screenshot',
|
||||
title: 'Screenshot',
|
||||
result: screenshotResult || lighthouseResults?.fullPageScreenshot?.screenshot,
|
||||
Component: ScreenshotCard,
|
||||
refresh: updateScreenshotResult,
|
||||
tags: ['client', 'meta'],
|
||||
}, {
|
||||
id: 'tls-cipher-suites',
|
||||
title: 'TLS Cipher Suites',
|
||||
result: tlsResults,
|
||||
Component: TlsCipherSuitesCard,
|
||||
refresh: updateTlsResults,
|
||||
tags: ['server', 'security'],
|
||||
}, {
|
||||
id: 'tls-security-config',
|
||||
title: 'TLS Security Issues',
|
||||
result: tlsResults,
|
||||
Component: TlsIssueAnalysisCard,
|
||||
refresh: updateTlsResults,
|
||||
tags: ['security'],
|
||||
}, {
|
||||
id: 'tls-client-support',
|
||||
title: 'TLS Handshake Simulation',
|
||||
result: tlsResults,
|
||||
Component: TlsClientSupportCard,
|
||||
refresh: updateTlsResults,
|
||||
tags: ['security'],
|
||||
}, {
|
||||
id: 'redirects',
|
||||
title: 'Redirects',
|
||||
result: redirectResults,
|
||||
Component: RedirectsCard,
|
||||
refresh: updateRedirectResults,
|
||||
tags: ['meta'],
|
||||
}, {
|
||||
id: 'linked-pages',
|
||||
title: 'Linked Pages',
|
||||
result: linkedPagesResults,
|
||||
Component: ContentLinksCard,
|
||||
refresh: updateLinkedPagesResults,
|
||||
tags: ['client', 'meta'],
|
||||
}, {
|
||||
id: 'robots-txt',
|
||||
title: 'Crawl Rules',
|
||||
result: robotsTxtResults,
|
||||
Component: RobotsTxtCard,
|
||||
refresh: updateRobotsTxtResults,
|
||||
tags: ['meta'],
|
||||
}, {
|
||||
id: 'status',
|
||||
title: 'Server Status',
|
||||
result: serverStatusResults,
|
||||
Component: ServerStatusCard,
|
||||
refresh: updateServerStatusResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'ports',
|
||||
title: 'Open Ports',
|
||||
result: portsResults,
|
||||
Component: OpenPortsCard,
|
||||
refresh: updatePortsResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'whois',
|
||||
title: 'Domain Info',
|
||||
result: whoIsResults,
|
||||
Component: WhoIsCard,
|
||||
refresh: updateWhoIsResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'txt-records',
|
||||
title: 'TXT Records',
|
||||
result: txtRecordResults,
|
||||
Component: TxtRecordCard,
|
||||
refresh: updateTxtRecordResults,
|
||||
tags: ['server'],
|
||||
}, {
|
||||
id: 'block-lists',
|
||||
title: 'Block Lists',
|
||||
result: blockListsResults,
|
||||
Component: BlockListsCard,
|
||||
refresh: updateBlockListsResults,
|
||||
tags: ['security', 'meta'],
|
||||
}, {
|
||||
id: 'features',
|
||||
title: 'Site Features',
|
||||
result: siteFeaturesResults,
|
||||
Component: SiteFeaturesCard,
|
||||
refresh: updateSiteFeaturesResults,
|
||||
tags: ['meta'],
|
||||
}, {
|
||||
id: 'sitemap',
|
||||
title: 'Pages',
|
||||
result: sitemapResults,
|
||||
Component: SitemapCard,
|
||||
refresh: updateSitemapResults,
|
||||
tags: ['meta'],
|
||||
}, {
|
||||
id: 'carbon',
|
||||
title: 'Carbon Footprint',
|
||||
result: carbonResults,
|
||||
Component: CarbonFootprintCard,
|
||||
refresh: updateCarbonResults,
|
||||
tags: ['meta'],
|
||||
},
|
||||
];
|
||||
|
||||
const MakeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => {
|
||||
const makeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => {
|
||||
const actions = [
|
||||
{ label: `Info about ${title}`, onClick: showInfo, icon: 'ⓘ'},
|
||||
{ label: `Re-fetch ${title} data`, onClick: refresh, icon: '↻'},
|
||||
@ -544,7 +857,7 @@ const Results = (): JSX.Element => {
|
||||
<Nav>
|
||||
{ address &&
|
||||
<Heading color={colors.textColor} size="medium">
|
||||
{ addressType === 'url' && <img width="32px" src={`https://icon.horse/icon/${makeSiteName(address)}`} alt="" /> }
|
||||
{ addressType === 'url' && <a href={address}><img width="32px" src={`https://icon.horse/icon/${makeSiteName(address)}`} alt="" /></a> }
|
||||
{makeSiteName(address)}
|
||||
</Heading>
|
||||
}
|
||||
@ -552,25 +865,60 @@ const Results = (): JSX.Element => {
|
||||
<ProgressBar loadStatus={loadingJobs} showModal={showErrorModal} showJobDocs={showInfo} />
|
||||
{ address?.includes(window?.location?.hostname || 'web-check.as93.net') && <SelfScanMsg />}
|
||||
<Loader show={loadingJobs.filter((job: LoadingJob) => job.state !== 'loading').length < 5} />
|
||||
<FilterButtons>{ showFilters ? <>
|
||||
<div className="one-half">
|
||||
<span className="group-label">Filter by</span>
|
||||
{['server', 'client', 'meta'].map((tag: string) => (
|
||||
<button
|
||||
key={tag}
|
||||
className={tags.includes(tag) ? 'selected' : ''}
|
||||
onClick={() => updateTags(tag)}>
|
||||
{tag}
|
||||
</button>
|
||||
))}
|
||||
{(tags.length > 0 || searchTerm.length > 0) && <span onClick={clearFilters} className="clear">Clear Filters</span> }
|
||||
</div>
|
||||
<div className="one-half">
|
||||
<span className="group-label">Search</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter Results"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<span className="toggle-filters" onClick={() => setShowFilters(false)}>Hide</span>
|
||||
</div>
|
||||
</> : (
|
||||
<div className="control-options">
|
||||
<span className="toggle-filters" onClick={() => setShowFilters(true)}>Show Filters</span>
|
||||
<a href="#view-download-raw-data"><span className="toggle-filters">Export Data</span></a>
|
||||
<a href="/about"><span className="toggle-filters">Learn about the Results</span></a>
|
||||
<a href="/about#additional-resources"><span className="toggle-filters">More tools</span></a>
|
||||
<a href="https://github.com/lissy93/web-check"><span className="toggle-filters">View GitHub</span></a>
|
||||
</div>
|
||||
) }
|
||||
</FilterButtons>
|
||||
<ResultsContent>
|
||||
|
||||
<Masonry
|
||||
<Masonry
|
||||
breakpointCols={{ 10000: 12, 4000: 9, 3600: 8, 3200: 7, 2800: 6, 2400: 5, 2000: 4, 1600: 3, 1200: 2, 800: 1 }}
|
||||
className="masonry-grid"
|
||||
columnClassName="masonry-grid-col">
|
||||
{
|
||||
resultCardData.map(({ id, title, result, refresh, Component }, index: number) => (
|
||||
(result && !result.error) ? (
|
||||
resultCardData
|
||||
.map(({ id, title, result, tags, refresh, Component }, index: number) => {
|
||||
const show = (tags.length === 0 || tags.some(tag => tags.includes(tag)))
|
||||
&& title.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
&& (result && !result.error);
|
||||
return show ? (
|
||||
<ErrorBoundary title={title}>
|
||||
<Component
|
||||
key={`${title}-${index}`}
|
||||
data={{...result}}
|
||||
title={title}
|
||||
actionButtons={refresh ? MakeActionButtons(title, refresh, () => showInfo(id)) : undefined}
|
||||
actionButtons={refresh ? makeActionButtons(title, refresh, () => showInfo(id)) : undefined}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
) : <></>
|
||||
))
|
||||
) : null})
|
||||
}
|
||||
</Masonry>
|
||||
</ResultsContent>
|
||||
|
@ -296,14 +296,6 @@ const docs: Doc[] = [
|
||||
],
|
||||
screenshot: 'https://i.ibb.co/k253fq4/Screenshot-from-2023-07-17-20-10-52.png',
|
||||
},
|
||||
{
|
||||
id: 'screenshot',
|
||||
title: 'Screenshot',
|
||||
description: 'This check takes a screenshot of webpage that the requested URL / IP resolves to, and displays it.',
|
||||
use: 'This may be useful to see what a given website looks like, free of the constraints of your browser, IP, or location.',
|
||||
resources: [],
|
||||
screenshot: 'https://i.ibb.co/2F0x8kP/Screenshot-from-2023-07-29-18-34-48.png',
|
||||
},
|
||||
{
|
||||
id: 'dns-server',
|
||||
title: 'DNS Server',
|
||||
@ -467,28 +459,65 @@ const docs: Doc[] = [
|
||||
title: 'Block Detection',
|
||||
description: 'Checks access to the URL using 10+ of the most popular privacy, malware and parental control blocking DNS servers.',
|
||||
use: '',
|
||||
resources: [],
|
||||
screenshot: '',
|
||||
resources: [
|
||||
{ title: 'ThreatJammer Lists', link: 'https://threatjammer.com/osint-lists'},
|
||||
],
|
||||
screenshot: 'https://i.ibb.co/M5JSXbW/Screenshot-from-2023-08-26-12-12-43.png',
|
||||
},
|
||||
{
|
||||
id: 'malware',
|
||||
id: 'threats',
|
||||
title: 'Malware & Phishing Detection',
|
||||
description: '',
|
||||
use: '',
|
||||
description: 'Checks if a site appears in several common malware and phishing lists, to determine it\'s threat level.',
|
||||
use: 'Knowing if a site is listed as a threat by any of these services can be useful for understanding the reputation of a site, and for identifying potential trends.',
|
||||
resources: [
|
||||
{ title: 'URLHaus', link: 'https://urlhaus-api.abuse.ch/'},
|
||||
{ title: 'PhishTank', link: 'https://www.phishtank.com/'},
|
||||
],
|
||||
screenshot: '',
|
||||
screenshot: 'https://i.ibb.co/hYgy621/Screenshot-from-2023-08-26-12-07-47.png',
|
||||
},
|
||||
// {
|
||||
// id: '',
|
||||
// title: '',
|
||||
// description: '',
|
||||
// use: '',
|
||||
// resources: [],
|
||||
// screenshot: '',
|
||||
// },
|
||||
{
|
||||
id: 'tls-cipher-suites',
|
||||
title: 'TLS Cipher Suites',
|
||||
description: 'These are combinations of cryptographic algorithms used by the server to establish a secure connection. It includes the key exchange algorithm, bulk encryption algorithm, MAC algorithm, and PRF (pseudorandom function).',
|
||||
use: 'This is important info to test for from a security perspective. Because a cipher suite is only as secure as the algorithms that it contains. If the version of encryption or authentication algorithm in a cipher suite have known vulnerabilities the cipher suite and TLS connection may then vulnerable to a downgrade or other attack',
|
||||
resources: [
|
||||
{ title: 'sslscan2 CLI', link: 'https://github.com/rbsec/sslscan' },
|
||||
{ title: 'ssl-enum-ciphers (NPMAP script)', link: 'https://nmap.org/nsedoc/scripts/ssl-enum-ciphers.html' }
|
||||
],
|
||||
screenshot: 'https://i.ibb.co/6ydtH5R/Screenshot-from-2023-08-26-12-09-58.png',
|
||||
},
|
||||
{
|
||||
id: 'tls-security-config',
|
||||
title: 'TLS Security Config',
|
||||
description: 'This uses guidelines from Mozilla\'s TLS Observatory to check the security of the TLS configuration. It checks for bad configurations, which may leave the site vulnerable to attack, as well as giving advice on how to fix. It will also give suggestions around outdated and modern TLS configs',
|
||||
use: 'Understanding issues with a site\'s TLS configuration will help you address potential vulnerabilities, and ensure the site is using the latest and most secure TLS configuration.',
|
||||
resources: [],
|
||||
screenshot: 'https://i.ibb.co/FmksZJt/Screenshot-from-2023-08-26-12-12-09.png',
|
||||
},
|
||||
{
|
||||
id: 'tls-client-support',
|
||||
title: 'TLS Handshake Simulation',
|
||||
description: 'This simulates how different clients (browsers, operating systems) would perform a TLS handshake with the server. It helps identify compatibility issues and insecure configurations.',
|
||||
use: '',
|
||||
resources: [
|
||||
{ title: 'TLS Handshakes (via Cloudflare Learning)', link: 'https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/' },
|
||||
{ title: 'SSL Test (via SSL Labs)', link: 'https://www.ssllabs.com/ssltest/' },
|
||||
],
|
||||
screenshot: 'https://i.ibb.co/F7qRZkh/Screenshot-from-2023-08-26-12-11-28.png',
|
||||
},
|
||||
{
|
||||
id: 'screenshot',
|
||||
title: 'Screenshot',
|
||||
description: 'This check takes a screenshot of webpage that the requested URL / IP resolves to, and displays it.',
|
||||
use: 'This may be useful to see what a given website looks like, free of the constraints of your browser, IP, or location.',
|
||||
resources: [],
|
||||
screenshot: 'https://i.ibb.co/2F0x8kP/Screenshot-from-2023-07-29-18-34-48.png',
|
||||
},
|
||||
];
|
||||
|
||||
export const featureIntro = [
|
||||
'When conducting an OSINT investigation on a given website or host, there are several key areas to look at. Each of these are documented below, along with links to the tools and techniques you can use to gather the relevant information.',
|
||||
'Web-Check can automate the process of gathering this data, but it will be up to you to interpret the results and draw conclusions.',
|
||||
];
|
||||
|
||||
export const about = [
|
||||
@ -496,24 +525,14 @@ export const about = [
|
||||
The core philosophy is simple: feed Web-Check a URL and let it gather, collate, and present a broad array of open data for you to delve into.`,
|
||||
|
||||
`The report shines a spotlight onto potential attack vectors, existing security measures,
|
||||
and the intricate web of connections within a site's architecture.
|
||||
and the web of connections within a site's architecture.
|
||||
The results can also help optimizing server responses, configuring redirects,
|
||||
managing cookies, or fine-tuning DNS records for your site.`,
|
||||
|
||||
`So, weather you're a developer, system administrator, security researcher, penetration
|
||||
tester or are just interested in discovering the underlying technologies of a given site
|
||||
- I'm sure you'll find this a useful addition to your toolbox.`,
|
||||
|
||||
`It works using a series of lambda functions, each of which makes a crafted fetch
|
||||
request to the host, processes the returned data, then responds with the results.
|
||||
The web UI is just a simple React TypeScript app.`,
|
||||
|
||||
`There's a managed instance (hosted on Netlify), which you can use for free
|
||||
(until my lambda function credits run out!), or you can easily deploy your own
|
||||
instance locally or remotely.
|
||||
All the code is open source, so feel free to fork and modify to your liking.
|
||||
For development and deployment instructions, as well as contributing guidelines, see the GitHub repo.
|
||||
`];
|
||||
];
|
||||
|
||||
export const license = `The MIT License (MIT)
|
||||
Copyright (c) Alicia Sykes <alicia@omg.com>
|
||||
@ -537,7 +556,8 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
`;
|
||||
|
||||
export const supportUs = [
|
||||
"The hosted app is free to use without restriction. All the code is open source, so you're also free to deploy your own instance, or make any modifications.",
|
||||
"Web-Check is free to use without restriction.",
|
||||
"All the code is open source, so you're also free to deploy your own instance, as well as fork, modify and distribute the code in both private and commerical settings.",
|
||||
"Running web-check does cost me a small amount of money each month, so if you're finding the app useful, consider <a href='https://github.com/sponsors/Lissy93'>sponsoring me on GitHub</a> if you're able to. Even just $1 or $2/month would be a huge help in supporting the ongoing project running costs.",
|
||||
"Otherwise, there are other ways you can help out, like submitting or reviewing a pull request to the <a href='https://github.com/Lissy93/web-check'>GitHub repo</a>, upvoting us on <a href='https://www.producthunt.com/posts/web-check'>Product Hunt</a>, or by sharing with your network.",
|
||||
"But don't feel obliged to do anything, as this app (and all my other projects) will always remain 100% free and open source, and I will do my best to ensure the managed instances remain up and available for as long as possible :)",
|
||||
|
Loading…
Reference in New Issue
Block a user