├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── test └── honeypot.js └── honeypot.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Piotr Rochala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | honeypot 2 | ========= 3 | 4 | Node.js implementation of the Project Honeypot (Http:BL) API. Because we all hate spam. 5 | 6 | - Utilizes Http:BL from known and loved https://www.projecthoneypot.org/ 7 | - Uses built-in node dns.resolve4 to get response from the DNS API 8 | - No Unicorns were harmed during development 9 | 10 | Installation 11 | -------------- 12 | 13 | ```sh 14 | npm install honeypot 15 | ``` 16 | 17 | Usage 18 | ---- 19 | ```javascript 20 | var honeypot = require('honeypot'); 21 | 22 | var pot = new honeypot('your_api_key'); 23 | 24 | pot.query('127.0.0.1', function(err, response){ 25 | if (!response) { 26 | console.log("IP not found in honeypot, we're all good!"); 27 | } else { 28 | console.log("Oh no, it's a spammer mate! Kil it with fire!"); 29 | console.log(response.getFormattedResponse()); 30 | // Suspicious, Comment Spammer 31 | // Threat Rating: 58 / 255 32 | // Recency: 1 / 255 33 | } 34 | }); 35 | ``` 36 | 37 | Example within Express 38 | ---- 39 | 40 | ```javascript 41 | var honeypot = require('honeypot'); 42 | 43 | var pot = new honeypot('your_api_key'); 44 | 45 | // example route for POST /comment/ 46 | create: function(req, res) { 47 | 48 | pot.query(req.ip, function(err, response){ 49 | if (!response) { 50 | console.log("IP not found in honeypot, we're all good!"); 51 | // do some commentary magic 52 | res.send({ msg: 'we hate spam!' }); 53 | } else { 54 | console.log("Die!"); 55 | res.send(null); 56 | } 57 | }); 58 | } 59 | ``` 60 | 61 | Kudos 62 | ---- 63 | 64 | Based on this sweet PHP gist https://gist.github.com/smithweb/7773373. 65 | 66 | 67 | License 68 | ---- 69 | 70 | MIT 71 | 72 | **Free Software, Hell Yeah!** 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "honeypot", 3 | "version": "0.0.2", 4 | "description": "Node.js implementation of the Project Honeypot (Http:BL) API. Because we all hate spam.", 5 | "keywords": [ 6 | "honeypot", 7 | "spam", 8 | "honey", 9 | "Http:BL", 10 | "ip", 11 | "spammer" 12 | ], 13 | "author": { 14 | "name": "Piotr Rochala", 15 | "email": "piotr.rochala@gmail.com", 16 | "url": "http://rocha.la/" 17 | }, 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "mocha": "*", 21 | "should": "*" 22 | }, 23 | "engines": { 24 | "node": "*" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/rochal/honeypot.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/rochal/honeypot/issues" 32 | }, 33 | "licenses": [ 34 | { 35 | "type": "MIT" 36 | } 37 | ], 38 | "main": "honeypot", 39 | "readme": "honeypot\n=========\n\nNode.js implementation of the Project Honeypot (Http:BL) API. Because we all hate spam.\n\n - Utilizes Http:BL from known and loved https://www.projecthoneypot.org/\n - Uses built-in node dns.resolve4 to get response from the DNS API\n - No Unicorns were harmed during development\n\nInstallation\n--------------\n\n```sh\nnpm install honeypot\n```\n\nUsage\n----\n```javascript\nvar honeypot = require('honeypot');\n\nvar pot = new honeypot('your_api_key');\n\npot.query('127.0.0.1', function(err, response){\n if (!response) {\n console.log(\"IP not found in honeypot, we're all good!\");\n } else {\n console.log(\"Oh no, it's a spammer mate! Kil it with fire!\");\n console.log(response.getFormattedResponse());\n // Suspicious, Comment Spammer\n // Threat Rating: 58 / 255\n // Recency: 1 / 255\n }\n});\n```\n\nExample within Express\n----\n\n```javascript\nvar honeypot = require('honeypot');\n\nvar pot = new honeypot('your_api_key');\n\n// example route for POST /comment/\ncreate: function(req, res) {\n \n pot.query(req.ip, function(err, response){\n if (!response) {\n console.log(\"IP not found in honeypot, we're all good!\");\n // do some commentary magic\n res.send({ msg: 'we hate spam!' });\n } else {\n console.log(\"Die!\");\n res.send(null);\n }\n });\n}\n```\n\nKudos\n----\n\nBased on this sweet PHP gist https://gist.github.com/smithweb/7773373.\n\n\nLicense\n----\n\nMIT\n\n**Free Software, Hell Yeah!**\n\n", 40 | "readmeFilename": "Readme.md", 41 | "_id": "honeypot@0.0.2", 42 | "_from": "honeypot@0.0.2", 43 | "scripts": {} 44 | } 45 | -------------------------------------------------------------------------------- /test/honeypot.js: -------------------------------------------------------------------------------- 1 | 2 | var should = require('should'), 3 | honeypot = require('../honeypot'); 4 | 5 | var pot = new honeypot('api_key'); 6 | 7 | describe('honeypot', function(){ 8 | 9 | it('honeypot object should be initialized', function(){ 10 | should(pot).be.ok; 11 | should(pot.getRawResponse()).be.ok; 12 | should(pot.getRawResponse().length).equal(0); 13 | }); 14 | 15 | it('honeypot should parse response for bunch of invalid IPs', function(){ 16 | 17 | var ip_response = { 18 | '194.90.36.155' : { // harvester 19 | response: [127, 1, 36, 3], 20 | visitor: 3, 21 | formattedVisitor: 'Suspicious, Harvester', 22 | threat: 36, 23 | recency: 1, 24 | isSearch: false 25 | }, 26 | '91.207.7.165' : { // spam server 27 | response: [127, 1, 58, 5], 28 | visitor: 5, 29 | formattedVisitor: 'Suspicious, Comment Spammer', 30 | threat: 58, 31 | recency: 1, 32 | isSearch: false 33 | }, 34 | '72.22.73.25' : { // bad web host 35 | response: [127, 1, 38, 1], 36 | visitor: 1, 37 | formattedVisitor: 'Suspicious', 38 | threat: 38, 39 | recency: 1, 40 | isSearch: false 41 | }, 42 | '62.210.123.137': { // comment spammer 43 | response: [127, 1, 45, 5], 44 | visitor: 5, 45 | formattedVisitor: 'Suspicious, Comment Spammer', 46 | threat: 45, 47 | recency: 1, 48 | isSearch: false 49 | }, 50 | '121.78.126.228': { // dictionary attack 51 | response: [127, 1, 56, 1], 52 | visitor: 1, 53 | formattedVisitor: 'Suspicious', 54 | threat: 56, 55 | recency: 1, 56 | isSearch: false 57 | }, 58 | '123.164.66.39' : { // rule breaker 59 | response: [127, 1, 47, 5], 60 | visitor: 5, 61 | formattedVisitor: 'Suspicious, Comment Spammer', 62 | threat: 47, 63 | recency: 1, 64 | isSearch: false 65 | }, 66 | '157.56.93.85' : { // crawler 67 | response: [127, 0, 8, 0], 68 | visitor: 0, 69 | formattedVisitor: 'Search Engine Bot (MSN)', 70 | threat: 8, 71 | recency: 0, 72 | isSearch: true 73 | } 74 | } 75 | 76 | // simulate response 77 | for (var i in ip_response) { 78 | var value = ip_response[i]; 79 | pot.setRawResponse(value.response); 80 | 81 | should(pot.isListed()).equal(true); 82 | should(pot.getVisitorType()).equal(value.visitor); 83 | should(pot.getFormattedVisitorType()).equal(value.formattedVisitor); 84 | should(pot.getThreatRating()).equal(value.threat); 85 | should(pot.getRecency()).equal(value.recency); 86 | should(pot.isSearchEngine()).equal(value.isSearch); 87 | } 88 | 89 | }); 90 | 91 | /** 92 | * NOTE: Tests below are performing actual dns call 93 | * - verify that IP is still classified as expected in test 94 | * https://www.projecthoneypot.org/ip_194.90.36.155 95 | * https://www.projecthoneypot.org/ip_91.207.7.165 96 | * - ensure api_key is updated 97 | */ 98 | 99 | // it('should do actual DNS call', function(done) { 100 | // pot.query('194.90.36.155', function(err, data) { 101 | // should(pot.isListed()).equal(true); 102 | // should(pot.getFormattedVisitorType()).equal('Suspicious, Harvester'); 103 | // done(); 104 | // }); 105 | // }); 106 | 107 | it('should do actual DNS call', function(done) { 108 | pot.query('91.207.7.165', function(err, data) { 109 | should(pot.isListed()).equal(true); 110 | should(pot.getFormattedVisitorType()).equal('Suspicious, Comment Spammer'); 111 | console.log(pot.getFormattedResponse()); 112 | done(); 113 | }); 114 | }); 115 | 116 | }) -------------------------------------------------------------------------------- /honeypot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Node Honeypot 2.3.0 4 | * Copyright 2014 Piotr Rochala 5 | * Based on original PHP class by smithweb 6 | * Available under MIT license 7 | */ 8 | var dns = require('dns'); 9 | 10 | module.exports = function(key) { 11 | 12 | var api_key = key; 13 | 14 | var visitor_type = { 15 | 0: 'Search Engine Bot', 16 | 1: 'Suspicious', 17 | 2: 'Harvester', 18 | 3: 'Suspicious, Harvester', 19 | 4: 'Comment Spammer', 20 | 5: 'Suspicious, Comment Spammer', 21 | 6: 'Harvester, Comment Spammer', 22 | 7: 'Suspicious, Harvester, Comment Spammer' 23 | }; 24 | 25 | var search_engine = { 26 | 0: 'Undocumented', 27 | 1: 'AltaVista', 28 | 2: 'Ask', 29 | 3: 'Baidu', 30 | 4: 'Excite', 31 | 5: 'Google', 32 | 6: 'Looksmart', 33 | 7: 'Lycos', 34 | 8: 'MSN', 35 | 9: 'Yahoo', 36 | 10: 'Cuil', 37 | 11: 'InfoSeek', 38 | 12: 'Miscellaneous' 39 | }; 40 | 41 | // Raw Response from http:BL query 42 | var _response = []; 43 | 44 | /** 45 | * Performs query of the httpBL service, using a DNS Query. 46 | * 47 | * See http://www.projecthoneypot.org/httpbl_api.php for 48 | * information on proper format and possible responses. 49 | * 50 | */ 51 | this.query = function(ip, callback) { 52 | 53 | var reversed_ip = ip.split('.').reverse().join('.') 54 | 55 | dns.resolve4([api_key, reversed_ip, 'dnsbl.httpbl.org'].join('.'), function(err, data) { 56 | if (data) { 57 | _response = data.toString().split('.').map(Number); 58 | callback(null, data); 59 | } else { 60 | callback(err, null); 61 | } 62 | }) 63 | } 64 | 65 | /** 66 | * Checks if the ip address was listed in the httpBL 67 | * 68 | * @return bool True if listed, False if not listed 69 | */ 70 | this.isListed = function() { 71 | if (_response[0] === 127) { 72 | return true; 73 | } 74 | return false; 75 | } 76 | 77 | /** 78 | * Returns vistor type as integer 79 | * 80 | * @return int|bool Vistor type or false if not in httBL 81 | */ 82 | this.getVisitorType = function() { 83 | if (this.isListed()) { 84 | return _response[3]; 85 | } 86 | return false; 87 | } 88 | 89 | /** 90 | * Returns string containing a text description of the visitor type 91 | * 92 | * @return string|bool Visitor type if listed in httpBL, false if not 93 | */ 94 | this.getFormattedVisitorType = function() { 95 | if (this.isListed()) { 96 | if (_response[3] === 0) { 97 | return visitor_type[_response[3]] + ' (' + search_engine[_response[2]] + ')'; 98 | } else { 99 | return visitor_type[_response[3]]; 100 | } 101 | } else { 102 | return false; 103 | } 104 | } 105 | 106 | /** 107 | * Gets the threat rating for an ip address if it is listed in the httpBL. 108 | * 109 | * @return int Threat score (out of a possible 255) 110 | */ 111 | this.getThreatRating = function() { 112 | if (this.isListed()) { 113 | return _response[2]; 114 | } 115 | return 0; 116 | } 117 | 118 | /** 119 | * Gets the number of days since an event was tracked for an ip address 120 | * if it is listed in the httpBL. 121 | * 122 | * @return int Number of days since most recent event (up to max of 255) 123 | */ 124 | this.getRecency = function() { 125 | if (this.isListed()) { 126 | return _response[1]; 127 | } 128 | return 0; 129 | } 130 | 131 | /** 132 | * Checks whether the ip address belongs to a search engine bot or company 133 | * 134 | * @return boolean True of ip belongs to search engine, false if not 135 | */ 136 | this.isSearchEngine = function() { 137 | if (this.isListed() && _response[3] === 0) { 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | /** 144 | * @return Array containing response details 145 | */ 146 | this.getRawResponse = function() { 147 | return _response; 148 | } 149 | 150 | /** 151 | * Sets raw response, useful for testing 152 | */ 153 | this.setRawResponse = function(value) { 154 | _response = value; 155 | } 156 | 157 | /* 158 | * Returns a formatted message with details about the IP address 159 | * 160 | * @param string format type of output for the response, text or html 161 | * @return string Formatted string of response info 162 | */ 163 | this.getFormattedResponse = function(format) { 164 | 165 | if (!format) { 166 | format = 'text'; 167 | } 168 | 169 | var line_end = "\n"; 170 | var output = ''; 171 | 172 | if (format == 'html') { 173 | line_end = "
\n"; 174 | } 175 | 176 | if (this.isListed()) { 177 | output += this.getFormattedVisitorType() + line_end; 178 | if (!this.isSearchEngine()) { 179 | output += "Threat Rating: " + this.getThreatRating() + " / 255" + line_end; 180 | output += "Recency: " + this.getRecency() + " / 255" + line_end; 181 | } 182 | } 183 | 184 | return output; 185 | } 186 | }; 187 | --------------------------------------------------------------------------------