En septembre 2025, l’un de nos clients nous a demandé de nous concentrer sur un produit spécifique : l’ActivID Appliance par HID. Selon le fournisseur, ce produit est utilisé dans le monde entier pour sécuriser l’accès aux infrastructures et aux données critiques. Il prend en charge une large gamme de méthodes d’authentification, notamment l’authentification push, le code à usage unique (OTP), les informations d’identification PKI et les informations d’identification statiques. Dans cet article, nous vous présenterons la méthodologie que nous avons utilisée pour découvrir HID-PSA-2025-002, un contournement d’authentification dans l’API SOAP pouvant mener à un accès administratif sur l’application.
Vous souhaitez améliorer vos compétences ? Découvrez nos sessions de formation ! …
En septembre 2025, l’un de nos clients nous a demandé de nous concentrer sur un produit spécifique : l’ActivID Appliance par HID. Selon le fournisseur, ce produit est utilisé dans le monde entier pour sécuriser l’accès aux infrastructures et aux données critiques. Il prend en charge une large gamme de méthodes d’authentification, notamment l’authentification push, le code à usage unique (OTP), les informations d’identification PKI et les informations d’identification statiques. Dans cet article, nous vous présenterons la méthodologie que nous avons utilisée pour découvrir HID-PSA-2025-002, un contournement d’authentification dans l’API SOAP pouvant mener à un accès administratif sur l’application.
Vous souhaitez améliorer vos compétences ? Découvrez nos sessions de formation ! En savoir plus
Avant-propos
Cet article est volontairement détaillé pour présenter autant d’aspects méthodologiques que possible concernant l’analyse de code en boîte blanche. Si seule la partie expliquant la vulnérabilité vous intéresse, veuillez vous référer à l’avis de sécurité HID-PSA-2025-002 ou sauter directement à la section d’investigation.
Identification du produit
Notre premier objectif était d’identifier la version exacte du produit, car plusieurs versions étaient disponibles sur la page de téléchargement de HID. La seule information que notre client nous avait fournie était l’URL de l’application. À partir de là, le seul artefact de version que nous avons remarqué était l’identifiant suivant, trouvé dans le footer HTML de l’instance de notre client :

Cet identifiant, v080620, **n’a pu être trouvé que dans la version 8.5 **de leur EULA (Contrat de Licence Utilisateur Final) au format PDF, disponible au téléchargement sur leur site web :

En suivant le guide d’installation, nous avons obtenu une instance virtualisée VMware de la solution dans sa version 8.5, nous donnant un accès root complet sur la machine.
Découverte de la surface d’attaque
Cette partie peut sembler assez ennuyeuse au premier abord, mais obtenir une bonne compréhension des routes et services exposés est une étape clé pour une analyse en boîte blanche. Dans le cas de cette recherche, nous pensons que l’analyse méticuleuse de chaque service a grandement contribué à la découverte de la vulnérabilité.
Étant donné que notre recherche se base sur la surface non authentifiée, à distance, un premier scan nmap est effectué :
$ nmap -p- -sV hid --open
PORT STATE SERVICE VERSION
40/tcp open ssh OpenSSH 7.4 (protocol 2.0)
443/tcp open ssl/http nginx
1005/tcp open ssl/http nginx
8443/tcp open ssl/http nginx
Une fois authentifié sur la VM (Machine Virtuelle), nous avons commencé à énumérer les services en écoute :
$ ss -ntalp
State Local Address:Port Process
LISTEN 0.0.0.0:3000 users:(("java",pid=3038,fd=58))
LISTEN 127.0.0.1:7800 users:(("java",pid=3038,fd=55))
LISTEN 127.0.0.1:25 users:(("master",pid=1821,fd=13))
LISTEN 0.0.0.0:61626 users:(("java",pid=3446,fd=990))
LISTEN 127.0.0.1:8443 users:(("java",pid=3446,fd=1009))
LISTEN 192.168.116.128:8443 users:(("java",pid=3446,fd=1008))
LISTEN 0.0.0.0:443 users:(("nginx",pid=1435,fd=6))
LISTEN 127.0.0.1:8445 users:(("java",pid=3446,fd=1010))
LISTEN 192.168.116.128:8445 users:(("java",pid=3446,fd=1007))
LISTEN 127.0.0.1:2016 users:(("oraagent.bin",pid=2049,fd=126))
LISTEN 127.0.0.1:50403 users:(("java",pid=3038,fd=57))
LISTEN 0.0.0.0:5093 users:(("lserv",pid=1357,fd=4))
LISTEN 0.0.0.0:40 users:(("sshd",pid=1360,fd=3))
LISTEN 127.0.0.1:9002 users:(("java",pid=3446,fd=694))
LISTEN 192.168.116.128:9002 users:(("java",pid=3446,fd=693))
LISTEN 0.0.0.0:1005 users:(("nginx",pid=1435,fd=8))
LISTEN 0.0.0.0:1006 users:(("nginx",pid=1435,fd=12))
LISTEN 127.0.0.1:56367 users:(("ocssd.bin",pid=2179,fd=137))
LISTEN 127.0.0.1:1007 users:(("miniserv.pl",pid=1858,fd=4))
LISTEN 0.0.0.0:61616 users:(("java",pid=3446,fd=989))
LISTEN 0.0.0.0:1008 users:(("nginx",pid=1435,fd=10))
LISTEN 192.168.116.128:1521 users:(("tnslsnr",pid=2080,fd=16))
LISTEN 127.0.0.1:1521 users:(("tnslsnr",pid=2080,fd=7))
LISTEN 0.0.0.0:1009 users:(("nginx",pid=1435,fd=14))
LISTEN *:50061 users:(("ora_d000_ftress",pid=2513,fd=44))
Pour faire court, les points suivants présentent un intérêt :
Les processus Java qui sont en écoute sur les ports 8443, 8445, 1005 et 1006.
Le proxy inverse Nginx qui a pu être vu sur le port 443 lors du scan nmap.
On peut remarquer que ces processus Java n’étaient pas détectés lors de l’utilisation de nmap, cela peut s’expliquer en listant les règles de pare-feu :
$ iptables -nvL
[...]
Chain IN_public_allow (1 references)
pkts bytes target prot opt in out source destination
15719 943K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 ctstate NEW,UNTRACKED
17 1020 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1005 ctstate NEW,UNTRACKED
0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1006 ctstate NEW,UNTRACKED
0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1812 ctstate NEW,UNTRACKED
10 536 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:40 ctstate NEW,UNTRACKED
0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW,UNTRACKED mark match 0x64
0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW,UNTRACKED mark match 0x65
Pour le protocole TCP, seuls les ports 40, 443 (HTTPS), 1005 et 1006 sont autorisés à être atteints.
Étant donné que le port 443 est ouvert sur internet, commençons par comprendre comment le proxy Nginx est configuré.
Investigation du service Nginx
L’analyse de la configuration Nginx commence par la lecture du fichier /etc/nginx :
user nginx;
worker_processes 2;
http {
include /etc/nginx/conf.d/*.conf;
}
Dans notre cas, ce fichier de configuration principal inclut uniquement tout autre fichier de configuration présent dans le dossier /etc/nginx/conf.d/ :
$ ls -l /etc/nginx/conf.d/*.conf
-rwx------ 1 root root 2005 Jun 20 2017 /etc/nginx/conf.d/default.conf
-rw-r--r-- 1 root root 652 Dec 18 2020 /etc/nginx/conf.d/web_activid.conf
Le fichier d’intérêt est web_activid.conf :
upstream weblogic8445 {
keepalive 30;
server 127.0.0.1:8445;
}
upstream weblogic8443 {
keepalive 30;
server 127.0.0.1:8443;
}
upstream webmin {
server 127.0.0.1:1007;
}
include /etc/nginx/include/TLS1.conf;
include /etc/nginx/include/TLS2.conf;
include /etc/nginx/include/TLS3.conf;
include /etc/nginx/include/MTLS1.conf;
include /etc/nginx/include/MTLS2.conf;
Deux déclarations d’upstream sont faites : weblogic8443 et weblogic8445, pointant respectivement vers des services HTTP sur les ports 8445 et 8443 de localhost. Les upstreams sont utilisés pour définir des serveurs qui peuvent ensuite être référencés dans l’ensemble de la configuration du serveur Nginx. Après cela, d’autres fichiers de configuration sont inclus, qui pourraient définir des services TLS et des services TLS avec authentification mutuelle, selon leur nommage.
Le fichier /etc/nginx/include/TLS1.conf est le suivant :
server {
listen 443 ssl ;
listen [::]:443 ssl ;
ssl_verify_client off;
# ...
set $upstream weblogic8445;
# ...
include /etc/nginx/include/app/TLS1/*.conf;
}
En ce qui concerne les autres fichiers de configuration TLS2.conf, TLS3.conf, MTL3.conf et MTLS2.conf, ceux-ci ne seront pas explorés, car ils font référence à des services qui ne sont pas accessibles depuis internet, ou qui nécessitent une authentification mutuelle à l’aide d’un certificat client.
Pour revenir à la configuration TLS1, ici, un serveur écoutant sur le port 443 est déclaré, il définit une variable $upsteam à weblogic8445, et certaines configurations de serveur situées dans /etc/nginx/include/app/TLS1 sont incluses :
$ ls -l /etc/nginx/include/app/TLS1
total 0
lrwxrwxrwx 1 root root 48 Sep 2 17:11 AuthenticationPortal.conf -> /etc/nginx/include/app/AuthenticationPortal.conf
lrwxrwxrwx 1 root root 39 Sep 2 17:11 HealthCheck.conf -> /etc/nginx/include/app/HealthCheck.conf
lrwxrwxrwx 1 root root 45 Sep 2 17:11 ManagementConsole.conf -> /etc/nginx/include/app/ManagementConsole.conf
lrwxrwxrwx 1 root root 32 Sep 2 17:11 Root.conf -> /etc/nginx/include/app/Root.conf
lrwxrwxrwx 1 root root 35 Sep 2 17:11 SCIMAPI.conf -> /etc/nginx/include/app/SCIMAPI.conf
lrwxrwxrwx 1 root root 45 Sep 2 17:11 SelfServicePortal.conf -> /etc/nginx/include/app/SelfServicePortal.conf
lrwxrwxrwx 1 root root 35 Sep 2 17:11 SoapAPI.conf -> /etc/nginx/include/app/SoapAPI.conf
Ces fichiers sont tous des liens symboliques vers leur fichiers équivalents dans /etc/nginx/include/app/. Par exemple, le fichier AuthenticationPortal.conf :
location /idp/ {
proxy_pass https://$upstream;
}
# OpenID
location ~ /idp/[^/]+/authn/ {
proxy_pass https://$upstream;
include /etc/nginx/include/openiderrorhandling.conf;
}
De manière similaire, le fichier SoapAPI.conf :
location ^~ /4TRESS/ {
proxy_pass https://$upstream;
limit_except POST {
deny all;
}
if ($request_uri ~* "(?:wsdl|xsd)$") {
return 404;
}
include /etc/nginx/include/soaperrorhandling.conf;
}
location ^~ /ac-iasp-backend-jaxws/ {
proxy_pass https://$upstream;
limit_except POST {
deny all;
}
if ($request_uri ~* "(?:wsdl|xsd)$") {
return 404;
}
include /etc/nginx/include/soaperrorhandling.conf;
}
Nous pouvons observer que ces fichiers de configuration suivent la même logique basée sur une directive location. Par exemple pour /idp/, ils re-routent la requête HTTP vers un serveur défini par une variable $upstream. Cette variable a été définie précédemment, en l’occurrence comme weblogic8445, qui pointe vers un serveur HTTP hébergé sur le port 8445 de localhost !
Après avoir extrait les chemins et les destinations, le routage suivant peut être observé :

Investigation des services Java
Lors de l’énumération des processus, le port 8445 était bien répertorié comme un processus Java. Les informations complètes sur ce processus sont récupérées comme suit :
$ lsof -i :8445
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 3301 ftuser 1005u IPv4 50538 0t0 TCP hid:copy (LISTEN)
$ ps -fwwp 3301
UID PID PPID C STIME TTY TIME CMD
ftuser 3301 3247 0 14:19 ? 00:02:29 /usr/java/default/bin/java -server -Xms2048m -Xmx2048m -Dweblogic.Name=ActivIDServer -Djava.security.policy=/opt/hid/weblogic/product/Oracle_Home/wlserver/server/lib/weblogic.policy -Dweblogic.ProductionModeEnabled=true -Djava.system.class.loader=com.oracle.classloader.weblogic.LaunchClassLoader -javaagent:/opt/hid/weblogic/product/Oracle_Home/wlserver/server/lib/debugpatch-agent.jar -da -Dwls.home=/opt/hid/weblogic/product/Oracle_Home/wlserver/server -Dweblogic.home=/opt/hid/weblogic/product/Oracle_Home/wlserver/server -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStore=/opt/hid/weblogic/activid/stores/truststore.jks -Djavax.net.ssl.trustStoreType=JKS -DPWB_CONFIG_FILE=/usr/local/activid/ActivID_AS/config/passphraseObfuscator.properties -Djava.net.preferIPv4Stack=true -Dactivid.home.dir=/usr/local/activid -Djava.awt.headless=true -Djava.library.path=/usr/local/activid/ActivID_AS/lib:/usr/local/lib:/usr/lib:: -Duser.domain=/opt/hid/weblogic/config/activid_domain -Dweblogic.Name=ActivIDServer -Dweblogic.security.SSL.minimumProtocolVersion=TLSv1.2 -Djava.awt.headless=true -Dactivid.enable.monitoring.servlet=true -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2 -Dhttps.cipherSuites=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA -Dweblogic.Stdout=/opt/hid/weblogic/activid/logs/weblogicStdout.log -Dweblogic.Stderr=/opt/hid/weblogic/activid/logs/weblogicStderr.log -Doracle.dms.context=OFF -DUseSunHttpHandler=true -Djava.security.auth.login.config=/opt/hid/weblogic/config/activid_domain/jaas.conf -Djava.security.egd=file:/dev/./urandom -Dweblogic.security.allowCryptoJDefaultPRNG=true -Djava.security.manager -Dweblogic.MaxMessageSize=41943040 weblogic.Server
Pour résumer, cette commande Java :
- Lance une instance du serveur web WebLogic via la classe
weblogic.Server - Définit certaines de ses propriétés et fonctionnalités (stdin, stdout, utilisation de la mémoire, suites de chiffrement, etc.)
- Configure un domaine situé à
/opt/hid/weblogic/config/activid_domain. - Configure certaines variables spécifiques aux applications, par exemple
activid.enable.monitoring.servlet
Pour WebLogic, un domaine n’est rien de moins qu’un dossier contenant toutes les ressources nécessaires pour instancier une ou plusieurs applications Java. Dans un tel dossier, selon la documentation, il devrait y avoir un fichier config.xmlau sein d’un dossier config :
$ ls -lah /opt/hid/weblogic/config/activid_domain/config/config.xml
-rw-rw---- 1 ftadmin ftgroup 12K Sep 2 17:15 /opt/hid/weblogic/config/activid_domain/config/config.xml
Ce fichier XML contient la liste de toutes les applications Java qui ont été lancées au sein du domaine, ainsi que d’autres paramètres (port d’écoute, format de journalisation, etc.). Ces applications peuvent, par exemple, être déployées en tant que :
Un WAR (Web Application aRchive) : un fichier d’archive qui contient une seule application web Java : fichiers HTML statiques, logique métier au sein de Servlets, fichiers JSP, librairies Java (extension jar), etc. Une telle application est configurée par un fichier de configuration, web.xml, situé à l’intérieur du fichier /WEB-INF/web.config, une fois le fichier .war extrait.
Un EAR (Enterprise Application aRchive) : un fichier d’archive contenant un ou plusieurs fichiers WAR à déployer, ou d’autres fonctionnalités Java telles que les EJBs (Enterprise Java Beans). Ce format est utilisé lorsqu’il est nécessaire de partager du code entre des applications, ou même simplement de regrouper un déploiement. Une telle application est configurée par son fichier de configuration /WEB-INF/application.xml, une fois le fichier .ear extrait.
Dans notre cas, la configuration est la suivante :
<server>
<name>ActivIDServer</name>
<ssl>
<name>ActivIDServer</name>
<listen-port>8445</listen-port>
</ssl>
<!-- ... -->
<app-deployment>
<name>4TRESS-EAR</name>
<module-type>ear</module-type>
<source-path>/opt/hid/weblogic/product/Oracle_Home/user_projects/applications/activid_domain/activid-authentication-services-weblogic.ear</source-path>
</app-deployment>
<app-deployment>
<name>ACTIVID-MC</name>
<module-type>war</module-type> <source-path>/opt/hid/weblogic/product/Oracle_Home/user_projects/applications/activid_domain/activid-management-console-weblogic.war</source-path>
</app-deployment>
<app-deployment>
<name>ACTIVID-SSP</name>
<module-type>war</module-type> <source-path>/opt/hid/weblogic/product/Oracle_Home/user_projects/applications/activid_domain/activid-self-service-portal-weblogic.war</source-path>
</app-deployment>
L’analyse de ce fichier confirme que l’application Java est bien à l’écoute sur le port 8445, et surtout donne le chemin d’accès aux fichiers EAR et WAR déployés, qui contiennent le code Java exécuté par l’application.

Architecture d’une application web Java
Le format EAR
Avant d’entrer dans le code Java, comprenons comment fonctionnent les applications Java. Pour cela, nous allons examiner l’architecture de activid-authentication-services-weblogic.ear :
$ unzip activid-authentication-services-weblogic.ear -d activid-authentication-services-weblogic
$ tree ./activid-authentication-services-weblogic/
./activid-authentication-services-weblogic
├── ac-4tress-core.jar
├── ac-4tress-scim-configuration.war
├── ac-4tress-scim.war
├── ac-iasp-backend.jar
├── activid-authentication-portal-weblogic.war
├── activid-health-check.war
├── lib
│ ├── ac-4tadap-samlproc.jar
│ ├── ac-4tadap-spi.jar
│ ├── ac-4tcore-api.jar
...
│ ├── commons-discovery-0.5.jar
│ ├── commons-fileupload-1.3.3.jar
│ └── xmlsec-2.2.0.jar
└── META-INF
├── application.xml
├── jboss-deployment-structure.xml
├── jboss-permissions.xml
├── MANIFEST.MF
├── was.policy
└── weblogic-application.xml
Lorsque vous dézippez un fichier EAR, vous visualisez une application d’entreprise complète packagée sous forme de multiples modules. Les fichiers .war sont les applications web (endpoints REST, interfaces utilisateur, servlets), tandis que les fichiers .jar comme ac-4tress-core.jar et ac-iasp-backend.jar sont des modules EJB qui fournissent les services backend utilisés par ces applications web.
Le répertoire lib/ contient les librairies partagées qui sont visibles par chaque module de l’EAR, permettant de centraliser le code commun au lieu de le dupliquer. Le répertoire META-INF/ contient les descripteurs de déploiement : weblogic-application.xml pour la configuration spécifique à WebLogic, divers descripteurs de fournisseurs pour d’autres conteneurs, et surtout application.xml, qui définit la structure de l’EAR en listant chaque module et en indiquant au serveur d’applications comment les déployer. Ce fichier est le suivant :
<application>
<display-name>ActivID Authentication Services</display-name>
<application-name>4TRESS-EAR</application-name>
<initialize-in-order>true</initialize-in-order>
<module>
<ejb>ac-4tress-core.jar</ejb>
</module>
<module>
<ejb>ac-iasp-backend.jar</ejb>
</module>
<module>
<web>
<web-uri>activid-health-check.war</web-uri>
<context-root>AIHealthCheck</context-root>
</web>
</module>
<module>
<web>
<web-uri>ac-4tress-scim.war</web-uri>
<context-root>scim</context-root>
</web>
</module>
<module>
<web>
<web-uri>ac-4tress-scim-configuration.war</web-uri>
<context-root>configuration</context-root>
</web>
</module>
<module>
<web>
<web-uri>activid-authentication-portal-weblogic.war</web-uri>
<context-root>idp</context-root>
</web>
</module>
</application>
Ici, nous trouvons plusieurs modules web, tels que l’application web activid-authentication-portal-weblogic.war. L’élément context-root indique le point de départ dans l’URL, qui est ici idp. Par conséquent, toute requête HTTP de la forme http://hid/idp/ sera transmise au WAR associé par WebLogic : activid-authentication-portal-weblogic.war.
Après avoir examiné ce XML, notre compréhension de l’application et de son architecture est désormais la suivante :

Le format WAR
Concernant le format WAR, nous prendrons l’exemple de activID-authentication-portal-weblogic.war :
$ mkdir activid-authentication-portal-weblogic
$ cd activid-authentication-portal-weblogic $ unzip ../activid-authentication-portal-weblogic.war
[...]
$ tree .
.
├── about.xhtml
├── auth
│ ├── actions-body.xhtml
│ [...]
│ └── reset-password.xhtml├── authn
│ ├── login.xhtml
│ └── token.xhtml
├── binding
│ ├── login-artifact.xhtml
│ [...]
│ ├── nameid-redirect.xhtml
│ └── single-logout-post.xhtml
├── common
│ ├── applet
│ │ └── applet.xhtml
│ ├── error.xhtml
│ └── required.xhtml
│ [...]
├── index.html
├── license.xhtml
├── META-INF
│ ├── MANIFEST.MF
│ └── was.policy
├── nocookie.xhtml
├── resources
│ ├── csrfguard.js
│ ├── css
│ │ └── theme.css
│ └── js
│ ├── base64url-arraybuffer.js
│ └── webauthn.js
├── timeout.xhtml
└── WEB-INF
├── beans.xml
├── ejb-jar.xml
├── jboss-web.xml
├── lib
│ ├── ac-iasp-frontend.jar
│ ├── ac-iasp-frontend-jaxws.jar
│ ├── ac-inputval-esapi.jar
│ ├── ac-oauth20sdk.jar
│ ├── ai-4tress-samlidp.jar
│ ├── commons-lang3-3.7.jar
│ ├── csrfguard.jar
│ ├── esapi-2.1.0.1.jar
│ ├── lang-tag-1.4.3.jar
│ ├── oauth2-oidc-sdk-5.63.jar
│ └── primefaces-6.2.jar
├── weblogic-ejb-jar.xml
├── weblogic.xml
└── web.xml
Nous commençons enfin à voir des fichiers liés aux applications web, comme des fichiers HTML (xhtml et html) qui affichent l’interface utilisateur. Par exemple, naviguer vers la page https://hid/idp/common/error.xhtml affiche :

Cette page correspond correctement au fichier activID-authentication-portal-weblogic/common/error.xhtml que nous venons d’extraire du WAR.
Dans l’arborescence des fichiers, le répertoire WEB-INF se distingue. Il contient les fichiers JAR pour toutes les dépendances locales à l’application, telles que les classes qui vont gérer les actions déclenchées par les fichiers xhtml ou les requêtes ciblant des Servlets Java web exposés. Les Servlets Java sont des classes qui exposent des méthodes accessibles via HTTP, le fondement des applications web Java.
Tandis que le déclenchement d’un fichier xhtml est direct (puisqu’il suffit de connaître le chemin du fichier et la configuration du module Java Faces), en ce qui concerne les Servlets, nous devons d’abord examiner le fichier web.xml pour comprendre ce qui se passe.
Ce fichier contient beaucoup d’informations pour cartographier la surface d’attaque, car il définit :
Les Servlets, spécifiés dans les balises <servlet>, associés à leur classe Java, ainsi que leur URL définie dans les balises <servlet-mapping>.
Les Filtres (balises <filter>) qui sont appliqués (à comprendre comme des middlewares) avant (requête HTTP) et après (réponse HTTP) les actions du servlet, ainsi que les URL pour lesquelles ils s’appliquent (balises <filter-mapping>).
L’instanciation des classes et des paramètres.
Disséquons celui issu du WAR activID-authentication-portal-weblogic (certaines parties sont tronquées pour plus de clarté) :
$ cat activid-authentication-portal-weblogic/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<display-name>ActivID Authentication Portal</display-name>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<!-- ... other servlets are declared -->
<servlet>
<description></description>
<display-name>SamlArtResolverServlet</display-name>
<servlet-name>SamlArtResolverServlet</servlet-name>
<servlet-class>com.actividentity.idp.servlet.SamlArtResolverServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SamlArtResolverServlet</servlet-name>
<url-pattern>/binding/artifact-resolution</url-pattern>
</servlet-mapping>
<filter>
<filter-name>ProtocolFilter</filter-name>
<filter-class>javax.faces.webapp.FacesServlet</filter-class>
</filter>
<!-- ... other filters are declared -->
<filter>
<filter-name>SecurityWrapper</filter-name>
<filter-class>com.hidglobal.ia.security.inputval.esapi.SecurityWrapper</filter-class>
</filter>
<filter-mapping>
<filter-name>ProtocolFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ... other filter mappings are declared -->
<filter-mapping>
<filter-name>Security Filter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<!-- ... -->
</web-app>
Ici, plusieurs Servlets sont définis. Par exemple, toute requête de la forme /idp/binding/artifact-resolution sera transmise au servlet com.actividentity.idp.servlet.SamlArtResolverServlet, car il est référencé dans le mapping. De la même manière, toute URL correspondant à /idp/*.xhtml sera transmise au servlet javax.faces.webapp.FacesServlet.
Cependant, avant d’atteindre ces classes, la requête HTTP passera par la liste de filtres déclarés, si ceux-ci correspondent au filter-mapping. Dans cet exemple, toute requête (/*) passera par le ProtocolFilter, puis par le Security Filter, dont le code est respectivement déclaré dans les classes javax.faces.webapp.FacesServlet et com.hidglobal.ia.security.inputval.esapi.SecurityWrapper.
Enfin, pour trouver quelle librairie Java (quel fichier JAR), déclare les classes identifiées, une simple commande grep suffit. Par exemple, avec com.actividentity.idp.servlet.SamlArtResolverServlet :
$ cd activid-authentication-portal-weblogic/WEB-INF/lib/
$ rg -al 'com.actividentity.idp.servlet.SamlArtResolverServlet'
ai-4tress-samlidp.jar
$ unzip -l ai-4tress-samlidp.jar
[...]
2961 2020-12-18 17:10 com/actividentity/idp/servlet/SamlArtResolverServlet.class
Maintenant que nous comprenons comment le fichier web.xml permet de mapper une URL à une classe Java, notre compréhension est désormais la suivante (notez que nous ne détaillons que partiellement la structure du WAR activid-authentication-portal-weblogic précédent) :

Décompilation 101
À ce stade, nous sommes désormais en mesure de comprendre le routage depuis Nginx jusqu’à un servlet spécifique, et donc la classe Java responsable de la logique métier. Cependant, nous devons maintenant être capables de lire le code Java afin d’identifier d’éventuelles vulnérabilités. C’est là qu’intervient la décompilation.
Plusieurs outils peuvent être utilisés pour décompiler le bytecode Java. Au cours de nos recherches, nous avons utilisé [vineflower](https://github.com/Vineflower/vineflower), un décompilateur Java moderne basé sur le décompilateur Fernflower de JetBrains.
Pour la décompilation, nous allons cibler les fichiers JAR EJB, ainsi que tous les fichiers JAR situés dans le répertoire lib du fichier WAR extrait :
$ ls -l
-rw-r--r-- 1 user user 3203575 Nov 17 16:08 ac-4tress-core.jar
-rw-r--r-- 1 user user 125721 Nov 17 16:08 ac-iasp-backend.jar
-rw-r--r-- 1 user user 39182 Nov 17 16:06 ac-iasp-frontend.jar
-rw-r--r-- 1 user user 1127763 Nov 17 16:06 ac-iasp-frontend-jaxws.jar
-rw-r--r-- 1 user user 60809 Nov 17 16:06 ac-inputval-esapi.jar
-rw-r--r-- 1 user user 39105 Nov 17 16:06 ac-oauth20sdk.jar
-rw-r--r-- 1 user user 434327 Nov 17 16:06 ai-4tress-samlidp.jar
-rw-r--r-- 1 user user 499634 Nov 17 16:06 commons-lang3-3.7.jar
-rw-r--r-- 1 user user 156911 Nov 17 16:06 csrfguard.jar
-rw-r--r-- 1 user user 395859 Nov 17 16:06 esapi-2.1.0.1.jar
-rw-r--r-- 1 user user 10621 Nov 17 16:06 lang-tag-1.4.3.jar
-rw-r--r-- 1 user user 418019 Nov 17 16:06 oauth2-oidc-sdk-5.63.jar
-rw-r--r-- 1 user user 4271042 Nov 17 16:06 primefaces-6.2.jar
$ fdfind '\.jar$' | parallel java -jar ~/tools/vineflower.jar {} ./out/
[...]
INFO: Decompiling class com/actividentity/service/iasp/frontend/wallet/jaxws/WalletService
INFO: ... done
INFO: Decompiling class com/actividentity/service/iasp/frontend/wallet/jaxws/package-info
[...]
INFO: ... done
Enfin, en utilisant un IDE tel que VSCode, le code Java de SamlArtResolverServlet peut par exemple être lu :

Mise en place du debug
Maintenant que nous avons clarifié la surface d’attaque et que nous sommes capables de lire le code Java, nous souhaitons ajouter un stub de débogage pour pouvoir définir des points d’arrêts depuis notre IDE favori pour une analyse plus poussée.
L’inspection des processus a révélé que notre application Java a été lancée depuis un processus dont le PID était 3315 :
$ ps -fwwp 3315
UID PID PPID C STIME TTY TIME CMD
ftuser 3315 1 0 02:40 ? 00:00:00 /bin/sh /opt/hid/weblogic/config/activid_domain/bin/startWebLogic.sh
$ cat /opt/hid/weblogic/config/activid_domain/bin/startWebLogic.sh
[...]
DOMAIN_HOME="/opt/hid/weblogic/config/activid_domain"
. ${DOMAIN_HOME}/bin/setDomainEnv.sh $*
SAVE_JAVA_OPTIONS="${JAVA_OPTIONS}"
[...]
${JAVA_HOME}/bin/java ${JAVA_VM} ${MEM_ARGS} -Dweblogic.Name=${SERVER_NAME} -Djava.security.policy=${WLS_POLICY_FILE} ${JAVA_OPTIONS} ${PROXY_SETTINGS} ${SERVER_CLASS} >"${WLS_REDIRECT_LOG}" 2>&1
Ce script bash startWebLogic.sh définit des variables d’environnement et des options qui sont ensuite transmises à la commande java. Nous pouvons donc détourner ce script afin d’ajouter un stub de débogage sur notre serveur WebLogic :
JAVA_OPTIONS="${SAVE_JAVA_OPTIONS} -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n"
En relançant le serveur web, nous pouvons confirmer que le port de notre JDWP est bien en écoute :
$ reboot
$ ss -untalp | grep 5005
tcp LISTEN 0 1 0.0.0.0:5005 0.0.0.0:* users:(("java",pid=3446,fd=628)
Nous avons également configuré notre IDE pour s’attacher à la JVM distante :

Rapide analyse with CodeQL
Avant de procéder à une revue manuelle approfondie, nous passons généralement le code à un outil SAST (Static Application Security Testing). Nous apprécions particulièrement CodeQL pour le code source Java, car il est gratuit, complet et bien maintenu par la communauté. Il permet d’écrire des requêtes dans un langage basé sur la logique pour trouver des schémas précis : chemins de désérialisation non sécurisés, concaténation de chaînes dangereuses dans du SQL, ou tout autre élément qui suit un modèle structurel dans le code. Si vous souhaitez plus de détails sur l’écriture de vos propres requêtes, n’hésitez pas à consulter la série complète "CodeQL Zero To Hero" sur le blog de CodeQL, ou l’excellent article de notre expert Hugo Vincent sur la découverte d’un gadget de désérialisation JNDI dans Wildfly à l’aide de CodeQL.
Création de la base de données
La première étape pour utiliser CodeQL sur un code source donné est de construire une base de données. Cette base de données contiendra des données interrogeables extraites de votre base de code. Plus précisément, elle contiendra une représentation complète et hiérarchique du code, y compris une représentation de l’AST** **(arbre syntaxique abstrait), du DFG (graphe de flux de données) et du CFG (graphe de flux de contrôle). Pour les langages compilés, l’exécution d’une analyse SAST implique le plus souvent de compiler le projet en utilisant le code source.
Cela pourrait être problématique pour nous, car nous ne disposons pas du code source, mais uniquement d’une version décompilée qui a été extraite des archives de l’application identifiée, et qui ne sera très probablement pas recompilable.
Cependant, en juin 2025, CodeQL a introduit une nouvelle fonctionnalité révolutionnaire appelée build-mode none. L’option build-mode none peut être utilisée pour créer des bases de données sans avoir à compiler le code source (le support est actuellement limité à C/C++, C#, Java et Rust).
Une fois que nous avons réussi à extraire les sources de l’application, nous avons créé une base de données CodeQL en utilisant la commande suivante :
$ codeql database create ~/codeql/databases/activid-authentication-services-weblogic-8.5 --language java --build-mode none -s activid-authentication-services-weblogic_vineflower/
Note : Lorsque vous travaillez sur de gros projets, il est généralement judicieux de restreindre l’analyse aux bibliothèques personnalisées et au code source de votre application. Pour ce faire, il suffit d’éviter de décompiler les bibliothèques tierces avant de créer votre base de données.
Lancer l’analyse
L’étape suivante consiste à exécuter les requêtes CodeQL sur notre base de données fraîchement construite.
Exécuter l’analyseur CodeQL tel quel, sans arguments supplémentaires, va détecter automatiquement le langage de la base de données et exécuter les requêtes par défaut sur la cible, ce qui constitue un bon point de départ. Cependant, nous aimons généralement exécuter des requêtes supplémentaires, telles que celles provenant de GithubSecurityLab, qui permettent d’identifier beaucoup plus de problèmes de sécurité.
Pour ce faire, il faut télécharger les packs appropriés :
$ codeql pack download "codeql/java-all@*"
$ codeql pack download "codeql/java-queries@*"
$ codeql pack download "githubsecuritylab/codeql-java-queries"
Ensuite, afin de lancer les requêtes de sécurité pré-construites qui proviennent des packs que nous venons de télécharger, nous pouvons utiliser les suites de requêtes CodeQL. Le fichier suivant, javasec.qls, est créé :
- queries: .
from: codeql/java-queries
- queries: '.'
from: githubsecuritylab/codeql-java-queries
- include:
kind:
- problem
- path-problem
tags contain: security
- exclude:
tags contain:
- debugging
- audit
- template
- exclude:
query path:
- /testing\/.*/
Enfin, nous lançons la suite de requêtes sur la base de données :
$ codeql database analyze ~/codeql/databases/activid-authentication-services-weblogic-8.5/ --format=sarif-latest -o activid-authentication-services-weblogic-8.5.sarif javasec.qls
Ici, nous sélectionnons le format de sortie SARIF (Static Analysis Results Interchange Format). Comme son nom l’indique, ce format est largement supporté par les outils SAST.
Note : L’analyse de la base de données peut être gourmande en ressources. Vous pouvez ajuster les ressources que vous allouez à CodeQL en utilisant les options --ram et --threads.
Interprétation des résultats
L’extension SARIF Viewer peut ensuite être utilisée pour visualiser les résultats :

Bien que certains résultats aient semblé prometteurs au départ, après une étape de vérification, la plupart des vulnérabilités signalées ont été qualifiées de faux positifs. Soit parce que nous n’avons trouvé aucun moyen d’atteindre le code mentionné, soit parce que la donnée altérée était assainie avant d’atteindre la fonction dangereuse finale.
Revue manuelle de l’application
Cette partie est généralement très chronophage. Nous n’entrerons pas dans trop de détails ici afin de maintenir la taille de cet article de blog à un niveau raisonnable. Il existe essentiellement deux approches qui peuvent être combinées. La première, l’approche top-down, consiste à suivre toutes les routes qui ont été identifiées lors de la phase de découverte de la surface d’attaque. La seconde, l’approche bottom-up, consiste à rechercher des fonctions ou des schémas vulnérables connus et à essayer d’identifier des chemins qui atteignent ces emplacements, comme nous l’avons fait initialement en utilisant CodeQL. Enfin, les deux approches peuvent être combinées en une approche hybride en rassemblant autant d’informations que possible pour tenter de relier les points d’entrée aux points de sortie, tout en gardant une très bonne compréhension du fonctionnement interne de l’application. Le débogueur peut être d’une grande aide lorsque vous essayez de comprendre comment atteindre une partie spécifique du code.
Nous avons passé beaucoup de temps sur les endpoints /ssp et /idp, car ces deux points d’accès sont intensivement utilisés par les utilisateurs finaux, en particulier lors de l’authentification.
Après avoir passé du temps à examiner le code sans succès, nous avons décidé d’essayer une approche différente et avons commencé à interagir dynamiquement avec tous les endpoints que nous avions détectés jusqu’à présent.
Au cours de notre analyse de la surface d’attaque, nous nous sommes souvenus que nous avions identifié certains endpoints SOAP, mis en évidence par le nom du fichier de configuration Nginx /etc/nginx/include/app/SoapAPI.conf. Cependant, nous n’avions pas réussi à interagir avec ceux-ci jusqu’à présent :
# ...
location ^~ /ac-iasp-backend-jaxws/ {
proxy_pass https://$upstream;
limit_except POST {
deny all;
}
if ($request_uri ~* "(?:wsdl|xsd)$") {
return 404;
}
include /etc/nginx/include/soaperrorhandling.conf;
}
Après quelques recherches sur Google, nous avons réalisé que JAXWS signifiait Jakarta XML Web Services. Il s’agit d’une méthode pour exposer des services SOAP en définissant des classes Java, c’est-à-dire de manière déclarative. Par exemple, le service UserManager est défini comme suit :
File: com/actividentity/service/iasp/backend/bean/UserManagerBean.java
34: @WebService(
35: name = "UserManager",
36: serviceName = "UserService",
37: targetNamespace = "http://jaxws.user.frontend.iasp.service.actividentity.com"
38: )
39: @HandlerChain(
40: file = "/LoginHandlerChain.xml"
41: )
42: @TransactionAttribute(TransactionAttributeType.NEVER)
43: public class UserManagerBean extends ProcessManager implements UserManagerLocal, UserManagerRemote
Comme l’indique la documentation, ce décorateur WebService devrait créer un endpoint nommé UserService, et sa documentation WSDL (Web Services Description Language) devrait être accessible à l’adresse /ac-iasp-backend-jaxws/UserService?wsdl. Nous avons donc d’abord modifié la configuration Nginx afin de permettre l’envoi d’autres verbes HTTP que POST, et de donner accès aux URL se terminant par wsdl ou xsd :
location ^~ /ac-iasp-backend-jaxws/ {
proxy_pass https://$upstream;
include /etc/nginx/include/soaperrorhandling.conf;
}
L’accès à l’endpoint de documentation SOAP UserService nous donne maintenant le résultat suivant :

Le fait d’avoir le format WSDL de l’endpoint SOAP nous a permis de lancer l’extension Burp Wsdler, afin d’analyser le WSDL et le XSD référencé et d’obtenir des requêtes HTTP pré-construites :

Les requêtes générées sont loin d’être parfaites, mais l’extension facilite grandement l’ensemble du processus de génération et permet d’interagir facilement avec les endpoints SOAP. Voici un exemple avec l’endpoint UserManager :

À ce stade, seul l’un d’entre nous s’est penché sur cette partie de l’application, et voici ce qui s’est passé :

En effet, sur l’instance de Vincent, la requête suivante a fonctionné :
POST /ac-iasp-backend-jaxws/UserManager HTTP/1.1
SOAPAction:
Content-Type: text/xml;charset=UTF-8
Host: hid:443
Content-Length: 391
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://jaxws.user.frontend.iasp.service.actividentity.com">
<soapenv:Header/>
<soapenv:Body>
<jax:findUserIds>
<arg0></arg0>
<!--type: long-->
<arg1>testu7</arg1>
<!--type: long-->
</jax:findUserIds>
</soapenv:Body>
</soapenv:Envelope>
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 09 Sep 2025 17:49:05 GMT
Content-Type: text/xml; charset=utf-8
Connection: keep-alive
X-ORACLE-DMS-ECID: 9eb94a90-9da2-4982-bc84-65fb42ed1688-0000029f
X-ORACLE-DMS-RID: 0
Content-Length: 953
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope
xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns1:findUserIdsResponse
xmlns:ns1="http://jaxws.user.frontend.iasp.service.actividentity.com">
<!-- ... -->
<return>
<id>spl-helpdesk</id>
<type>User</type>
</return>
<return>
<id>spl-stp</id>
<type>User</type>
</return>
<return>
<id>spl-api</id>
<type>User</type>
</return>
<return>
<id>spl-cmsadmin</id>
<type>User</type>
</return>
<return>
<id>sys15188351801401656</id>
<type>User</type>
</return>
<return>
<id>spl-useradmin</id>
<type>User</type>
</return>
</ns1:findUserIdsResponse>
</S:Body>
</S:Envelope>
Alors que du côté de Pierre, une erreur serveur 503 est obtenue !
Investigation du comportement suspect
Afin d’interagir avec les classes Java internes responsables des opérations métier, l’application ActivID utilise plusieurs EJB qui sont accessibles via HTTP grâce à JAXWS. Comme nous l’avons vu, ces endpoints sont accessibles via /ac-iasp-backend-jaxws/*.
Analyse de l’authentification aux services JAXWS
L’application d’authentification ActivID expose des EJBs via JAXWS en annotant certaines de ses classes avec @WebService.
Par exemple, la classe com.aspace.ftress.interfaces70.ejb.bean.UserManagerBean :
File: com/actividentity/service/iasp/backend/bean/UserManagerBean.java
34: @WebService(
35: name = "UserManager",
36: serviceName = "UserService",
37: targetNamespace = "http://jaxws.user.frontend.iasp.service.actividentity.com" 38: )
39: @HandlerChain(
40: file = "/LoginHandlerChain.xml"
41: )
42: @TransactionAttribute(TransactionAttributeType.NEVER)
43: public class UserManagerBean extends ProcessManager implements UserManagerLocal, UserManagerRemote
Lors de l’accès à l’EJB demandé, JAXWS exécute d’abord la HandlerChain, qui agit comme un middleware avant d’exécuter l’endpoint SOAP. Dans ce cas, la HandlerChain pointe vers le fichier LoginHandlerChain.xml, défini comme suit :
File: ac-iasp-backend.jar::LoginHandlerChain.xml
1: <?xml version="1.0" encoding="UTF-8"?>
2: <jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee">
3: <!-- Note: The '*" denotes a wildcard. -->
4: <jws:handler-chain name="LoginHandlerChain">
5: <jws:handler>
6: <jws:handler-class>com.actividentity.service.iasp.backend.handler.LoginHandler</jws:handler-class>
7: </jws:handler>
8: </jws:handler-chain>
9: </jws:handler-chains>
Seul un handler est déclaré, com.actividentity.service.iasp.backend.handler.LoginHandler, et il effectue les opérations suivantes :
File: com/actividentity/service/iasp/backend/handler/LoginHandler.java
31: public class LoginHandler implements SOAPHandler<SOAPMessageContext> {
32: private static Logger logger = LogManager.getInstance().getLogger(LoginHandler.class);
33: private static final QName subjectName = new QName("mySubjectUri", "mySubjectHeader"); // [A.4]
[...]
39:
40: public boolean handleMessage(SOAPMessageContext var1) {
41: this.readMessage(var1); // [A.1]
42: return true;
[...]
51:
52: private void readMessage(SOAPMessageContext var1) {
53: logger.debug("LoginHandler: Read SOAP header");
54: Boolean var2 = (Boolean)var1.get("javax.xml.ws.handler.message.outbound");
55: if (!var2) {
56: try {
57: SOAPMessage var3 = var1.getMessage();
58: SOAPEnvelope var4 = var3.getSOAPPart().getEnvelope();
59: SOAPHeader var5 = var4.getHeader();
60: Iterator var6 = var5.examineAllHeaderElements();
61:
62: while (var6.hasNext()) { // [A.2]
63: SOAPHeaderElement var7 = (SOAPHeaderElement)var6.next();
64: if (var7.getElementQName().equals(subjectName)) { // [A.3]
65: logger.debug("LoginHandler: header found, unmarshall it");
66: JAXBElement var8 = jc.createUnmarshaller().unmarshal(var7, MySubject.class); // [A.5]
67: var8.getDeclaredType();
68: MySubject var9 = (MySubject)var8.getValue(); // [A.6]
69: this.setSubject(var9); // [A.7]
70: break;
71: }
72: }
73: } catch (Exception var10) {
74: SubjectHolder.setSubject(null); // [A.8]
75: logger.error("LoginHandler: error getting subject in SOAP header", var10);
76: }
Un tel handler qui implémente l’interface SOAPHandler exécutera la méthode LoginHandler.handleMessage dès réception d’une requête HTTP SOAP. Dans le cas de ce handler, au niveau de [A.1], la méthode readMessage est appelée. Ensuite, à [A.2], le code itère sur les éléments soapenv:header, et recherche une entrée ([A.3]) qui correspond à mySubjectHeader, lequel est défini à [A.4]. Puis, il appelle la méthode com.actividentity.service.iasp.backend.handler.LoginHandler.setSubject à [A.7] avec l’objet subject fourni par l’utilisateur, qui est* unmarshal*é ([A.5]) puis récupéré en [A.6]. Enfin, si une exception est levée, la méthode LoginHandler.setSubject est appelée avec un Subject nul, et la fonction de l’EJB est ensuite exécuté. La méthode LoginHandler.setSubject est définie comme suit :
File: com/actividentity/service/iasp/backend/handler/LoginHandler.java
080: private void setSubject(MySubject var1) {
081: logger.trace("LoginHandler.setSubject: Begin");
082: Subject var2 = new Subject(); // [B.1]
[...]
092: ChannelCodePrincipal var11 = new ChannelCodePrincipal(var1.getChannel());
093: ALSIPrincipal var12 = new ALSIPrincipal(var1.getUserAlsi()); // [B.2]
[…]
100: var2.getPrincipals().add(var12); // [B.3]
[...]
106: logger.trace("LoginHandler.setSubject: End");
107: SubjectHolder.setSubject(var2); // [B.4]
108: }
À [B.1], une instance de javax.security.auth.Subject est créée à partir de l’instance MySubject fournie. Ensuite, à [B.2], la valeur ALSI fournie par l’utilisateur est récupérée, puis définie sur l’instance Subject.
Cette valeur ALSI imprévisible est utilisée par l’application comme session stockée côté serveur.
En [B.3], la méthode statique SubjectHolder.setSubject est appelée :
File: com/actividentity/service/iasp/util/security/SubjectHolder.java
01: package com.actividentity.service.iasp.util.security;
02:
03: import javax.security.auth.Subject;
04:
05: public class SubjectHolder {
06: static ThreadLocal<Subject> subject = new ThreadLocal<>(); // [C.2]
07:
08: publi