-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrunner.js
228 lines (189 loc) · 12 KB
/
runner.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/*jshint loopfunc: true */
(function () {
'use strict';
var fs = require('fs');
var util = require('util');
var xml2js = require('xml2js');
var colors = require('colors');
var mongoose = require('mongoose');
var cipherInfo = require('./mapCiphers');
var child_process = require('child_process');
var exec = require('child_process').exec;
var MongoClient = require('mongodb').MongoClient;
var spawn = require('child-process-promise').spawn;
var Scan = require('./schemas/scanSchema');
var Domain = require('./schemas/domainSchema');
var parser = new xml2js.Parser();
var wipMap = {};
var args = process.argv.slice(2);
var jobs = parseInt(args[0] || 50);
// here we are going to save our temporary files
child_process.execSync('mkdir -p tmp', { encoding: 'utf8' });
mongoose.connect('mongodb://localhost:27017/tls', function(err) {
if (err) throw err;
// start the work
for (var i = 0; i < jobs; i++) {
setTimeout(function() {
workOnNextDomain();
}, 100*i);
}
});
var workOnNextDomain = function() {
// find a domain on which no one is working
Domain.findOne({wip: false}).sort({lastScanDate: 1}).then(function(domain) {
if (!domain) {
console.log("no domain found");
}
if (domain.domain in wipMap) {
// someone is already working on this domain, take another one
workOnNextDomain();
} else {
console.log(Date(), domain.domain, '✔︎'.green, 'domain received');
// no-one is working on this domain, mark it as wip and start the work
wipMap[domain.domain] = true;
domain.wip = true;
domain.lastScanDate = Date();
domain.save().then(function(rDomain){
console.log(Date(), domain.domain, '✔︎'.green, 'WIP flag set');
// start the actual scanning
scan(domain);
});
}
});
};
var scan = function(domain) {
var scan = new Scan();
var xmlFileName = util.format(__dirname+'/tmp/%s.xml', domain.domain);
// setup the scan object
scan.domain = domain.domain;
scan.tld = scan.domain.split('.')[scan.domain.split('.').length-1];
scan.sources = domain.sources;
scan.scanDate = new Date();
// execute SSLScan
console.log(Date(), domain.domain, '-> starting SSLScan'.yellow);
spawn('./sslscan/sslscan', ['--no-colour', '--no-heartbleed', util.format('--xml=%s', xmlFileName), domain.domain], {capture: [ 'stdout', 'stderr' ], encoding: 'utf8'})
.then(function (result) {
if (result.stderr.length > 0) {
// SSLScan executed with errors errors
console.log(Date(), domain.domain, 'X SSLScan had problems on this url:'.red, result.stderr);
scan.scanError = true;
scan.scanErrorMessage = result.stderr;
insertScan(scan);
workOnNextDomain();
} else {
// SSLScan executed without errors
try {
// read the xml and parse it to json
var xmlFile = fs.readFileSync(xmlFileName, 'utf8');
parser.parseString(xmlFile, function(err, result) {
if (err) console.log(Date(), 'parseErr'.red, err);
if (!result.document.ssltest) return console.log('parseErr'.red, 'result.document.ssltest is undefined');
// get some more certificate information via OpenSLL
var publicKeyAlgorithm = '';
var publicKeyLength = 0;
var getCertCmd = 'openssl s_client -connect ' + domain.domain + ':443 </dev/null 2>/dev/null | openssl x509 -text -noout';
console.log(Date(), domain.domain, '->'.yellow, 'executing', getCertCmd);
exec(getCertCmd, function(error, stdout, stderr) {
if (error || stderr.length > 0) {
return console.log("CERT READING ERR".red, error, stderr);
// TODO: mark fail for this scan
} else {
var x509Output = stdout;
// no wrestle through the x509 output and collect our data (algo & keylength)
var algoPattern = 'Public Key Algorithm: ';
var algoPos = x509Output.indexOf(algoPattern) + algoPattern.length;
var lineEndPos = x509Output.indexOf('\n',algoPos);
publicKeyAlgorithm = x509Output.substring(algoPos, lineEndPos);
// no get the key size
var followingLine = x509Output.substring(lineEndPos, x509Output.indexOf('\n',lineEndPos+1)).trim();
var publicKeyKeylengthAsString = followingLine.substring(followingLine.indexOf('(')+1, followingLine.indexOf(' bit)'));
publicKeyLength = parseInt(publicKeyKeylengthAsString);
console.log(Date(), domain.domain, '✔︎'.green, 'Public Key:', publicKeyAlgorithm, publicKeyLength);
// add cert informations
scan.certificate = {};
if (result.document.ssltest[0].certificate) {
if (result.document.ssltest[0].certificate[0].altnames)
scan.certificate.altnames = result.document.ssltest[0].certificate[0].altnames[0];
if (result.document.ssltest[0].certificate[0].expired)
scan.certificate.expired = result.document.ssltest[0].certificate[0].expired[0];
if (result.document.ssltest[0].certificate[0].issuer)
scan.certificate.issuer = result.document.ssltest[0].certificate[0].issuer[0];
if (result.document.ssltest[0].certificate[0]['self-signed'])
scan.certificate.selfSigned = result.document.ssltest[0].certificate[0]['self-signed'][0];
if (result.document.ssltest[0].certificate[0]['not-valid-after'])
scan.certificate.notValidAfter = result.document.ssltest[0].certificate[0]['not-valid-after'][0];
if (result.document.ssltest[0].certificate[0]['not-valid-before'])
scan.certificate.notValidBefore = result.document.ssltest[0].certificate[0]['not-valid-before'][0];
if (result.document.ssltest[0].certificate[0]['signature-algorithm'])
scan.certificate.signatureAlgorithm = result.document.ssltest[0].certificate[0]['signature-algorithm'][0];
if (result.document.ssltest[0].certificate[0].subject)
scan.certificate.subject = result.document.ssltest[0].certificate[0].subject[0];
scan.certificate.publicKeyAlgorithm = publicKeyAlgorithm;
scan.certificate.publicKeyLength = publicKeyLength;
}
// collect all the ciphers suites
scan.ciphers = [];
// check if there actually is a ciphers array, some server provide no ciphers
if (typeof result.document.ssltest[0].cipher !== 'undefined') {
for (var i = 0; i < result.document.ssltest[0].cipher.length; i++) {
var cipher = {};
cipher.cipher = result.document.ssltest[0].cipher[i].$.cipher;
cipher.protocol = result.document.ssltest[0].cipher[i].$.sslversion;
cipher.status = result.document.ssltest[0].cipher[i].$.status;
cipher.curve = result.document.ssltest[0].cipher[i].$.curve;
cipher.bits = parseInt(result.document.ssltest[0].cipher[i].$.bits);
// read key exchange strength
if (result.document.ssltest[0].cipher[i].$.ecdhebits) {
// when ECDH is used as key exchange
cipher.kxStrength = parseInt(result.document.ssltest[0].cipher[i].$.ecdhebits);
} else if (result.document.ssltest[0].cipher[i].$.dhebits) {
// when DHE is used as key exchange
cipher.kxStrength = parseInt(result.document.ssltest[0].cipher[i].$.dhebits);
} else {
// when neither ECDH nor DHE is used, its RSA and this means
// the client use the servers public key for key exchange
cipher.kxStrength = scan.certificate.publicKeyLength;
}
// get some additional cipher info (actualy its informations from the tls specs)
var additionalCipherInfo = cipherInfo.getCipherInfos(cipher.cipher);
if (additionalCipherInfo) {
cipher.kx = additionalCipherInfo.kx;
cipher.au = additionalCipherInfo.au;
cipher.enc = additionalCipherInfo.enc;
cipher.mac = additionalCipherInfo.mac;
cipher.export = additionalCipherInfo.export;
}
// push the cipher to the hosts ciphers array
scan.ciphers.push(cipher);
}
}
console.log(Date(), domain.domain, '✔︎'.green, 'ciphers found:', scan.ciphers.length);
// insert the new scan into DB
scan.scanError = false;
insertScan(scan);
}
});
});
} catch (e) {
console.log(Date(), domain.domain, 'X JSERR', JSON.stringify(e).substring(0,100));
} finally {
// delete the from SSLScan generated xml file
child_process.execSync(util.format('rm -f %s', xmlFileName), { encoding: 'utf8' });
// remove WIP flag and move to next domain
delete wipMap[scan.domain];
domain.wip = false;
domain.save();
// work on the next document
workOnNextDomain();
}
}
});
};
// TODO: move this to a mongoose schema method
var insertScan = function(scan, cb) {
scan.save(function (err, rScan) {
if (err) return console.log(Date(), scan.domain, 'X'.red, 'Error while inserting the new Scan', err);
console.log(colors.green('%s %s ✔︎ Scan inserted in DB %s'), Date(), scan.domain, rScan.id.toString());
});
};
}());