UNPKG

@hclsoftware/secagent

Version:

IAST agent

170 lines (151 loc) 7.39 kB
//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