saksh-secure
Version:
A Node.js tool to investigate login form security and performance issues
209 lines (192 loc) • 7.41 kB
JavaScript
const axios = require('axios').create({ jar: require('tough-cookie').CookieJar() });
const { performance } = require('perf_hooks');
const tough = require('tough-cookie');
async function testBasicLogin(config) {
const startTime = performance.now();
try {
const response = await axios.post(config.targetUrl, {
[config.usernameField]: config.commonUsernames[0],
[config.passwordField]: config.commonPasswords[0]
}, { timeout: config.timeout });
return {
status: response.status,
success: response.data.success || false,
message: response.data.message || 'No message',
responseTime: `${(performance.now() - startTime).toFixed(2)}ms`
};
} catch (error) {
return {
status: error.response?.status || 'N/A',
error: error.message,
responseTime: `${(performance.now() - startTime).toFixed(2)}ms`
};
}
}
async function testSqlInjection(config) {
const results = [];
for (const payload of config.sqlInjectionPayloads) {
const result = await testBasicLogin({ ...config, commonUsernames: [payload], commonPasswords: [payload] });
results.push({
payload,
...result,
potentialVulnerability: result.success || result.message.includes('success')
});
}
return results;
}
async function testXssVulnerability(config) {
const results = [];
for (const payload of config.xssPayloads) {
const result = await testBasicLogin({ ...config, commonUsernames: [payload], commonPasswords: [payload] });
const isReflected = result.message?.includes(payload) || result.error?.includes(payload);
results.push({
payload,
...result,
potentialXss: isReflected || result.status === 200
});
}
return results;
}
async function testSessionManagement(config) {
try {
const response = await axios.post(config.targetUrl, {
[config.usernameField]: config.commonUsernames[0],
[config.passwordField]: config.commonPasswords[0]
}, { timeout: config.timeout });
const cookies = response.headers['set-cookie'] || [];
const cookieDetails = cookies.map(cookie => {
const parsed = tough.parse(cookie);
return {
name: parsed?.key,
secure: parsed?.secure || false,
httpOnly: parsed?.httpOnly || false,
sameSite: parsed?.sameSite || 'None'
};
});
return {
status: response.status,
cookiesSet: cookieDetails,
secureCookies: cookieDetails.every(c => c.secure && c.httpOnly),
message: response.data.message || 'No message'
};
} catch (error) {
return {
status: error.response?.status || 'N/A',
error: error.message,
cookiesSet: []
};
}
}
async function testCsrfProtection(config) {
try {
const response = await axios.post(config.targetUrl, {
[config.usernameField]: config.commonUsernames[0],
[config.passwordField]: config.commonPasswords[0]
}, { timeout: config.timeout, maxRedirects: 0 });
return {
status: response.status,
message: response.data.message || 'No message',
csrfProtection: response.status === 403 || response.data.message.includes('CSRF')
};
} catch (error) {
return {
status: error.response?.status || 'N/A',
error: error.message,
csrfProtection: error.response?.status === 403 || error.message.includes('CSRF')
};
}
}
async function testPasswordPolicy(config) {
const results = [];
for (const password of config.weakPasswords) {
const result = await testBasicLogin({ ...config, commonPasswords: [password] });
results.push({
password,
...result,
policyEnforced: result.message.includes('password') && result.status !== 200
});
}
return results;
}
async function testRateLimiting(config) {
const results = [];
for (let i = 0; i < config.maxAttempts + 2; i++) {
const result = await testBasicLogin(config);
results.push({
attempt: i + 1,
...result,
rateLimitDetected: result.status === 429 || result.message.includes('too many requests'),
captchaDetected: result.message.includes('CAPTCHA') || result.message.includes('verify')
});
await new Promise(resolve => setTimeout(resolve, 500));
}
return results;
}
async function testHttpsEnforcement(config) {
const httpUrl = config.targetUrl.replace('https://', 'http://');
try {
const response = await axios.post(httpUrl, {
[config.usernameField]: config.commonUsernames[0],
[config.passwordField]: config.commonPasswords[0]
}, { timeout: config.timeout, maxRedirects: 0 });
return {
status: response.status,
message: 'HTTP request succeeded',
httpsEnforced: false
};
} catch (error) {
return {
status: error.response?.status || 'N/A',
error: error.message,
httpsEnforced: error.message.includes('redirect') || error.response?.status === 301 || error.response?.status === 302
};
}
}
async function testAccountLockout(config) {
const results = [];
for (let i = 0; i < 10; i++) {
const result = await testBasicLogin({ ...config, commonPasswords: ['wrongpassword'] });
results.push({
attempt: i + 1,
...result,
lockoutDetected: result.message.includes('locked') || result.status === 403
});
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}
async function testPerformance(config) {
const results = [];
const testCases = [
{ username: config.commonUsernames[0], password: config.commonPasswords[0] },
{ username: 'invalid', password: 'invalid' },
{ username: 'x'.repeat(1000), password: 'x'.repeat(1000) }
];
for (const testCase of testCases) {
const times = [];
for (let i = 0; i < 5; i++) {
const startTime = performance.now();
await testBasicLogin({ ...config, commonUsernames: [testCase.username], commonPasswords: [testCase.password] });
times.push(performance.now() - startTime);
await new Promise(resolve => setTimeout(resolve, 500));
}
results.push({
testCase,
averageResponseTime: (times.reduce((a, b) => a + b, 0) / times.length).toFixed(2) + 'ms',
maxResponseTime: Math.max(...times).toFixed(2) + 'ms'
});
}
return results;
}
module.exports = {
testBasicLogin,
testSqlInjection,
testXssVulnerability,
testSessionManagement,
testCsrfProtection,
testPasswordPolicy,
testRateLimiting,
testHttpsEnforcement,
testAccountLockout,
testPerformance
};