Compare commits

...

19 Commits

Author SHA1 Message Date
3a31cd4540 Replace Bad Web Bot report category with Hacking 2026-01-17 10:15:24 +01:00
c876cbedc4 Remove accidental report import 2026-01-17 09:28:51 +01:00
3bd264f974 Make sure to use the right HTTP method for the report endpoint 2026-01-17 09:23:15 +01:00
21c3d2e194 Implement AbuseIPDB reporting 2026-01-17 08:45:23 +01:00
71312297be Fix invalid port in healthcheck 2026-01-15 09:42:09 +01:00
58b8cf3bdc Add healthcheck 2026-01-15 09:41:02 +01:00
38e5654341 Reintroduce logging and add SIGTERM handler 2026-01-15 09:34:17 +01:00
efbcc5a7fb Temporarily disable access logs 2025-11-28 13:22:12 +01:00
20881c1ba4 Add User-Agent logging 2025-10-02 08:56:26 +02:00
ed11b2d15d Update message and max delay 2025-09-27 10:38:03 +02:00
a9ee81c3c4 Add (UN)LICENSE 2025-09-27 10:37:42 +02:00
e96d3092f5 Bump min and max delay to 3000 and 7000 ms respectively 2024-08-10 00:50:35 +02:00
b7c9ff1c68 Send the message only once to let fail2ban do its thing 2024-07-30 17:37:51 +02:00
8b64852e34 Introduce random delays between res writes 2024-07-26 16:08:05 +02:00
90a9b31d07 Use Polish date format in the logs 2024-07-25 23:04:36 +02:00
184546335d Read targeted header from x-forwarded-host 2024-07-24 19:21:05 +02:00
52ea928c86 Temporarily log all request headers 2024-07-24 19:19:39 +02:00
3c60b4fe84 Use uppercase letters for targeted host header 2024-07-24 19:18:07 +02:00
94ef11a0c8 Log time and targeted host 2024-07-24 19:15:07 +02:00
4 changed files with 123 additions and 13 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

View File

@@ -1,7 +1,15 @@
FROM node:lts-alpine
USER node
WORKDIR /app
ENV NODE_ENV production
ENV NODE_ENV=production
ENV TZ=Europe/Warsaw
COPY --chown=node:node index.mjs ./
EXPOSE 3000
CMD ["node", "./index.mjs"]
HEALTHCHECK \
--interval=10s \
--timeout=5s \
--start-period=3s \
--retries=3 \
CMD ["wget", "http://localhost:3000/health", "-O", "/dev/null", "-q"]

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>

101
index.mjs
View File

@@ -1,23 +1,100 @@
import { createServer } from 'node:http';
import { clearInterval, setInterval } from 'node:timers';
import { setTimeout } from 'node:timers';
const msg = 'Relax, take it easy! For there is nothing that we can do.';
const minDelayMs = 3000;
const maxDelayMs = 6000;
const delayDiff = maxDelayMs - minDelayMs;
const randomDelay = () => Math.floor(Math.random() * delayDiff + minDelayMs);
const ipNextReportDateMap = new Map();
const server = createServer((req, res) => {
const connOpenDate = new Date();
const endpoint = `${req.method} ${req.url}`;
if (endpoint === 'GET /health') {
res.statusCode = 200;
return res.end('OK\n');
}
const ip = req.headers['x-forwarded-for'];
const userAgent = req.headers['user-agent'];
const host = req.headers['x-forwarded-host'];
console.log(
`Caught ${req.headers['x-forwarded-for']} on ${req.method} ${req.url}`
`${ip} (${userAgent}) targeted ${host} on ${endpoint}`
);
let msg = ':) you are an idiot hahahahaha :)';
let charIdx = 0;
let intervalId = setInterval(() => {
if (charIdx === msg.length) {
charIdx = 0;
res.write('\n');
} else {
res.write(msg[charIdx++]);
}
}, 3000);
const hang = () => {
if (res.closed) return;
else if (charIdx === msg.length) res.end('\n');
else res.write(msg[charIdx++]);
res.once('close', () => clearInterval(intervalId));
setTimeout(hang, randomDelay());
};
hang();
res.once('close', async () => {
const connCloseDate = new Date();
const diffText = new Date(connCloseDate - connOpenDate)
.toISOString()
.substring(14, 19);
const nextIpReportDate = ipNextReportDateMap.get(ip) || connCloseDate;
const hangResult =
charIdx === msg.length ? 'received the message' : 'aborted connection';
console.log(`${ip} ${hangResult} after ${diffText}`);
if (connCloseDate < nextIpReportDate) return;
const queryParams = new URLSearchParams();
queryParams.append('ip', ip);
queryParams.append('categories', '15,21');
queryParams.append('timestamp', connOpenDate.toISOString());
queryParams.append(
'comment',
`Vulnerability scanner detected!\nUser-Agent: ${userAgent}\nEndpoint: ${endpoint}`
);
const abuseIpDbRes = await fetch(
`https://api.abuseipdb.com/api/v2/report?${queryParams.toString()}`,
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Key': process.env.ABUSEIPDB_API_KEY,
}
}
);
if (abuseIpDbRes.ok) {
const reportDate = new Date();
console.log(`${ip} has been reported!`);
ipNextReportDateMap.set(
ip,
new Date(
reportDate.getFullYear(),
reportDate.getMonth(),
reportDate.getDate() + 1,
reportDate.getHours(),
reportDate.getMinutes(),
reportDate.getSeconds()
)
);
} else {
console.error(
`Failed to report ${ip}: ${abuseIpDbRes.status} ${abuseIpDbRes.statusText}`
);
}
});
});
server.listen(3000);
process.on('SIGTERM', () => server.close());