diff --git a/src/app.ts b/src/app.ts index 740478b..db6207f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -57,6 +57,7 @@ export const serve = async (electronApp?: any): Promise => { app.set('views', path.join(__dirname, 'views/pages')); app.locals.environment = process.env.NODE_ENV; app.locals.announcement = config.announcement; + app.locals.hasSlackWebhook = typeof config.apiKeys.Slack_Webhook === 'string'; /* Compress pages */ app.use(require('compression')()); diff --git a/src/utils/config.ts b/src/utils/config.ts index ca3fa49..1a48713 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -17,6 +17,8 @@ export interface Config { Google_SafeBrowsing: string; Github_WebHook: string; VirusTotal: string; + Google_Captcha: string; + Slack_Webhook: string; }; autoPull: { enabled: boolean; @@ -56,7 +58,9 @@ if (!fs.existsSync('./config.json')) { apiKeys: { Google_SafeBrowsing: undefined, Github_WebHook: undefined, - VirusTotal: undefined + VirusTotal: undefined, + Google_Captcha: undefined, + Slack_Webhook: undefined }, autoPull: { enabled: false }, lookups: { @@ -77,6 +81,12 @@ if (!fs.existsSync('./config.json')) { if (!config.apiKeys.VirusTotal) { debug('Warning: No VirusTotal API key found'); } + if (!config.apiKeys.Google_Captcha) { + debug('Warning: No Google Captcha secret found'); + } + if (!config.apiKeys.Slack_Webhook) { + debug('Warning: No Slack Webhook found'); + } if (config.lookups.DNS.servers.length > 0) { dns.setServers(config.lookups.DNS.servers); } diff --git a/src/utils/gcaptcha.ts b/src/utils/gcaptcha.ts new file mode 100644 index 0000000..69a9122 --- /dev/null +++ b/src/utils/gcaptcha.ts @@ -0,0 +1,32 @@ +import * as request from 'request'; +import config from './config'; +import * as Debug from 'debug'; + +const debug = Debug('gcaptcha'); + +/** + * Verify a Google Captcha response + */ +export const verifyResponse = (response: string): Promise => { + return new Promise((resolve, reject) => { + if (config.apiKeys.Google_Captcha) { + request.post( + 'https://www.google.com/recaptcha/api/siteverify?secret=' + + encodeURIComponent(config.apiKeys.Google_Captcha) + + '&response=' + + encodeURIComponent(response), + { json: true }, + (err, response, body) => { + if (err) { + reject(err); + } else { + debug(body); + resolve(body.success); + } + } + ); + } else { + reject('No Google Captcha secret found!'); + } + }); +}; diff --git a/src/utils/router.ts b/src/utils/router.ts index 2e6b3d9..2ad81e6 100644 --- a/src/utils/router.ts +++ b/src/utils/router.ts @@ -8,6 +8,8 @@ import * as url from 'url'; import config from './config'; import * as github from './github'; import * as isIpPrivate from 'private-ip'; +import * as captcha from './gcaptcha'; +import * as slack from './slack'; import { getGoogleSafeBrowsing, getURLScan, getVirusTotal } from './lookup'; const router = express.Router(); @@ -30,15 +32,15 @@ router.get('/api/', (req, res) => res.render('api')); /* Report pages */ router.get('/report/', (req, res) => res.render('report')); -router.get('/report/domain/:domain', (req, res) => +router.get('/report/domain/:domain?', (req, res) => res.render('report', { - domain: req.params.domain + domain: req.params.domain || true }) ); -router.get('/report/address/:address', (req, res) => +router.get('/report/address/:address?', (req, res) => res.render('report', { - address: req.params.address + address: req.params.address || true }) ); @@ -447,6 +449,40 @@ router.get('/api/v1/check/:search', (req, res) => { } }); +/* Incoming user reports */ +router.post('/api/v1/report/', async (req, res) => { + if ( + config.apiKeys.Google_Captcha && + config.apiKeys.Slack_Webhook && + req.body && + req.body.args && + req.body.args.captcha + ) { + const isValidCaptcha = await captcha.verifyResponse(req.body.args.captcha); + if (isValidCaptcha) { + slack.sendReport(req.body); + res.json({ + success: true + }); + } else { + res.json({ + success: false, + message: 'Invalid captcha response provided' + }); + } + } else if (config.apiKeys.Slack_Webhook && req.body && req.body.args && req.body.args.captcha) { + slack.sendReport(req.body); + res.json({ + success: true + }); + } else { + res.json({ + success: false, + message: 'No captcha response provided' + }); + } +}); + /* Redirect old API requests */ router.get('/api/:all*?', (req, res) => res.redirect('/api/v1/' + req.params.all)); diff --git a/src/utils/slack.ts b/src/utils/slack.ts new file mode 100644 index 0000000..c8ae218 --- /dev/null +++ b/src/utils/slack.ts @@ -0,0 +1,135 @@ +import * as request from 'request'; +import config from './config'; +import * as Debug from 'debug'; + +const debug = Debug('slack'); + +/** + * Send report through Slack + */ +export const sendReport = (report: any): Promise => { + return new Promise((resolve, reject) => { + if (config.apiKeys.Slack_Webhook) { + let message = ''; + if (report.reportType == 'generalDomainReport') { + message += '*Domain*: '; + message += report.args.domain || '(none)'; + message += '\n'; + message += '*Reason*: '; + message += report.args.reason || '(none)'; + } else if (report.reportType == 'generalAddressReport') { + message += '*Address*: '; + if (report.args.address) { + message += ' + '' + ) + .join(', '); + } else { + message += '(none)'; + } + } else if (report.reportType == 'urgentMessageAddressReport') { + message += '*Reason*: '; + message += report.args.message || '(none)'; + message += '\n'; + message += '*Victim address*: '; + if (report.args.from) { + message += ' + '' + ) + .join(', '); + } else { + message += '(none)'; + } + } else if (report.reportType == 'urgentDomainAddressReport') { + message += '*Reason*: '; + message += report.args.message || '(none)'; + message += '\n'; + message += '*Victim address*: '; + if (report.args.from) { + message += ' + '' + ) + .join(', '); + } else { + message += '(none)'; + } + } else { + message += '*Unknown reportType*: `' + report.reportType + '`\n\n'; + report.args.captcha = null; + message += '```' + JSON.stringify(report.args, null, 4) + '```'; + } + request.post( + config.apiKeys.Slack_Webhook, + { + json: true, + body: { + text: message + } + }, + (err, response, body) => { + if (err) { + reject(err); + } else { + resolve(body); + } + } + ); + } else { + reject('No Slack webhook found!'); + } + }); +}; diff --git a/src/views/pages/report.ejs b/src/views/pages/report.ejs index 3c36963..7b37b23 100644 --- a/src/views/pages/report.ejs +++ b/src/views/pages/report.ejs @@ -263,7 +263,7 @@
Domain:
- <% if(typeof domain !== "undefined") { %> + <% if(typeof domain === "string") { %> <% } else { %> @@ -290,7 +290,7 @@
Address:
- <% if(typeof address !== "undefined") { %> + <% if(typeof address === "string") { %> <% } else { %> @@ -391,10 +391,23 @@ + - + <% if(typeof domain !== "undefined") { %> <% } else if(typeof address !== "undefined") { %> diff --git a/src/views/static/js/report.js b/src/views/static/js/report.js index df28b05..8d20f6e 100644 --- a/src/views/static/js/report.js +++ b/src/views/static/js/report.js @@ -4,7 +4,7 @@ var args = {}; function finish() { $(".captcha").fadeOut('', function() { $(".loading").fadeIn('', function() { - $.post("https://lu1t.nl/report.php", { + $.post(reportEndpoint, { reportType: reportType, args: args }).done(function(data) { diff --git a/src/views/static/js/reportaddress.js b/src/views/static/js/reportaddress.js index 708b246..11b19e5 100644 --- a/src/views/static/js/reportaddress.js +++ b/src/views/static/js/reportaddress.js @@ -3,7 +3,7 @@ var args = {}; function finish() { $(".captcha").fadeOut('', function() { $(".loading").fadeIn('', function() { - $.post("https://lu1t.nl/report.php", { + $.post(reportEndpoint, { reportType: 'generalAddressReport', args: args }).done(function(data) { diff --git a/src/views/static/js/reportdomain.js b/src/views/static/js/reportdomain.js index 49b8f2c..2719d91 100644 --- a/src/views/static/js/reportdomain.js +++ b/src/views/static/js/reportdomain.js @@ -4,7 +4,7 @@ var args = {}; function finish() { $(".captcha").fadeOut('', function() { $(".loading").fadeIn('', function() { - $.post("https://lu1t.nl/report.php", { + $.post(reportEndpoint, { reportType: 'generalDomainReport', args: args }).done(function(data) {