@hclsoftware/secagent
Version:
IAST agent
170 lines (151 loc) • 7.39 kB
JavaScript
//IASTIGNORE
/*
* ****************************************************
* Licensed Materials - Property of HCL.
* (c) Copyright HCL Technologies Ltd. 2017, 2025.
* Note to U.S. Government Users *Restricted Rights.
* ****************************************************
*/
'use strict'
const {IASTHashMap} = require('./Utils/IASTHashMap')
const {IASTHashSet} = require('./Utils/IASTHashSet')
const setCookie = require('set-cookie-parser')
const CookieIast = require('./CookieIAST')
const TaintTracker = require('./TaintTracker')
const Entity = require('./Entity')
const Vulnerability = require('./Vulnerability')
const { ConfigInfo } = require('./ConfigFile/ConfigInfo')
const userNameVariants = ['username', 'user', 'usr', 'user_name', 'user-name', 'mail', 'email', 'e-mail', 'e_mail']
const passwordVariants = ['password', 'pwd', 'pass', 'passw', 'passwd', 'psswd', 'pswd', 'passwrd']
const inSessionPattern = new RegExp('(?<!script)(>|[ <](alt|title|id|value|class)=[\"\']?)[^<\"]*(log[_-\\s]?out|sign([_-\\s])?out|log[_-\\s]?off|sign[_-\\s]?off|exit|quit|bye-bye|clearuser|invalidate)<?', 'is')
const suspectedLogins = new IASTHashMap()
const usedSessionCookies = new IASTHashSet()
function getNameFromVariants (parameterNames, variants) {
return parameterNames.find(parameterName => variants.origArrayIncludes(parameterName.origToLowerCase()))
}
function collectSessionCookiesFromObject (cookies, result) {
Object.keys(cookies).filter(key => SessionTracker.isSessionCookie(key)).forEach(key => result.add(new CookieIast(key, cookies[key])))
}
class SessionTracker {
static findSessionCookies (request) {
const res = new IASTHashSet()
if (request.cookies != null) {
collectSessionCookiesFromObject(request.cookies, res)
}
if (request.signedCookies != null) {
collectSessionCookiesFromObject(request.signedCookies, res)
}
return res
}
static isLoginRequest (requestInfo) {
const requestParams = requestInfo.allParameters
const parameterNames = Object.keys(requestParams)
const userNameParam = this.getUserNameFromVariants(parameterNames)
if (userNameParam == null || !requestInfo.isUsedParameter(userNameParam)){
return false
}
const passwordParam = this.getPasswordFromVariants(parameterNames)
return passwordParam != null && requestInfo.isUsedParameter(passwordParam)
}
static isPasswordName(parameterName){
return parameterName != null && SessionTracker.getPasswordFromVariants([parameterName]) != null
}
static getUserNameFromVariants (parameterNames) {
return getNameFromVariants(parameterNames, userNameVariants)
}
static getPasswordFromVariants (parameterNames) {
return getNameFromVariants(parameterNames, passwordVariants)
}
static registerLogin (cookieSet) {
if (cookieSet.length !== 0) {
suspectedLogins.set(cookieSet, false)
}
}
static collectResponseSessionCookies (response, requestInfo) {
const headers = response.getHeaders()
const combinedCookieHeader = headers['set-cookie']
const splitCookieHeaders = setCookie.splitCookiesString(combinedCookieHeader)
const responseCookiesArr = setCookie.parse(splitCookieHeaders)
const responseCookieSet = new IASTHashSet()
responseCookiesArr.filter(cookie => SessionTracker.isSessionCookie(cookie.name)).forEach(cookie => {
const iastCookie = new CookieIast(cookie.name, cookie.value)
responseCookieSet.add(iastCookie)
if (usedSessionCookies.contains(iastCookie)) {
TaintTracker.reportStackLessVulnerability(Vulnerability.SESSION_FIXATION, iastCookie.toString(), '', null, null, false, new Entity.Entity(iastCookie.name, iastCookie.value, Entity.EntityType.COOKIE), requestInfo)
} else {
usedSessionCookies.add(iastCookie)
}
})
return responseCookieSet
}
static addExistingCookiesToResponseCookies (responseSessionCookies, requestSessionCookies) {
for (const requestCookie of requestSessionCookies) {
let changedInResponse = false
for (const responseCookie of responseSessionCookies) {
if (responseCookie.name === requestCookie.name) {
changedInResponse = true
break
}
}
if (!changedInResponse) {
responseSessionCookies.add(requestCookie)
}
}
}
static trackSession (responseStatus, requestSessionCookies, responseSessionCookies, responseText, requestInfo) {
let sessionCookies = requestSessionCookies
// check if cookie was changed during the handling of this request
if (requestSessionCookies.length !== 0 && suspectedLogins.containsKey(requestSessionCookies)) {
if (!requestSessionCookies.equals(responseSessionCookies)) {
suspectedLogins.delete(requestSessionCookies)
suspectedLogins.set(responseSessionCookies, true)
sessionCookies = responseSessionCookies
}
}
const responseClass = Math.floor(responseStatus / 100)
const success = responseClass === 2 // check status code is 2**
if (sessionCookies != null && (success || responseClass === 4 || responseClass === 5)) {
// got success/error code, check for vulnerability and stop tracking login.
const sessionIdChanged = suspectedLogins.get(sessionCookies)
if (sessionIdChanged != null && !sessionIdChanged && success && this.responseTextMatchesInSessionPattern(responseText)) {
// if login is successful and session cookie has not changed we report session fixation.
TaintTracker.reportStackLessVulnerability(Vulnerability.SESSION_FIXATION, `[${sessionCookies.join(', ')}]`, '', null, null, true, new Entity.Entity('', '', Entity.EntityType.NO_TYPE), requestInfo)
}
suspectedLogins.delete(sessionCookies)
}
}
static isSessionCookie (cookieName) {
const lowerCaseName = cookieName.origToLowerCase()
return (lowerCaseName.origStringIncludes('session') && lowerCaseName.origStringIncludes('id')) || lowerCaseName.origStringIncludes('connect.sid')
}
/*
* Expensive function! (the call to Match).
* make sure there is no more efficient solution before you call it.
*/
static responseTextMatchesInSessionPattern (responseText) {
if (responseText == null) {
return false
}
const userConfigPatterns = ConfigInfo.ConfigInfo.inSessionPatterns
if (userConfigPatterns != null) {
if (userConfigPatterns.some(pattern => responseText.origStringIncludes(pattern))) {
return true
}
}
return responseText.origMatch(inSessionPattern)
}
static ProcessResponse (req, res, requestInfo, responseText) {
const requestSessionCookies = SessionTracker.findSessionCookies(req)
if (requestSessionCookies.length !== 0) {
if (SessionTracker.isLoginRequest(requestInfo)) {
SessionTracker.registerLogin(requestSessionCookies)
}
}
const responseSessionCookies = SessionTracker.collectResponseSessionCookies(res, requestInfo)
// this makes the variable responseSessionCookies contain the all the session ID cookies that should be set in the client.
SessionTracker.addExistingCookiesToResponseCookies(responseSessionCookies, requestSessionCookies)
SessionTracker.trackSession(res.statusCode, requestSessionCookies, responseSessionCookies, responseText, requestInfo)
}
}
module.exports = SessionTracker
module.exports.inSessionPattern = inSessionPattern