22
33import static com .datadog .iast .util .StringUtils .endsWithIgnoreCase ;
44import static com .datadog .iast .util .StringUtils .substringTrim ;
5+ import static datadog .trace .api .gateway .Events .EVENTS ;
56import static datadog .trace .api .telemetry .LogCollector .SEND_TELEMETRY ;
67
78import com .datadog .iast .Dependencies ;
89import com .datadog .iast .model .Evidence ;
910import com .datadog .iast .model .Location ;
1011import com .datadog .iast .model .Vulnerability ;
1112import com .datadog .iast .model .VulnerabilityType ;
13+ import datadog .trace .api .function .TriFunction ;
14+ import datadog .trace .api .gateway .CallbackProvider ;
15+ import datadog .trace .api .gateway .Events ;
16+ import datadog .trace .api .gateway .Flow ;
17+ import datadog .trace .api .gateway .RequestContext ;
18+ import datadog .trace .api .gateway .RequestContextSlot ;
1219import datadog .trace .api .iast .sink .ApplicationModule ;
1320import datadog .trace .bootstrap .instrumentation .api .AgentSpan ;
1421import datadog .trace .bootstrap .instrumentation .api .AgentTracer ;
22+
23+ import java .io .ByteArrayInputStream ;
1524import java .io .File ;
25+ import java .io .FileInputStream ;
1626import java .io .IOException ;
27+ import java .io .InputStream ;
28+ import java .io .StringReader ;
1729import java .nio .charset .StandardCharsets ;
30+ import java .nio .file .DirectoryStream ;
1831import java .nio .file .FileVisitOption ;
1932import java .nio .file .FileVisitResult ;
2033import java .nio .file .FileVisitor ;
2134import java .nio .file .Files ;
2235import java .nio .file .Path ;
2336import java .nio .file .Paths ;
2437import java .nio .file .attribute .BasicFileAttributes ;
38+ import java .util .ArrayDeque ;
39+ import java .util .ArrayList ;
2540import java .util .Arrays ;
2641import java .util .Collection ;
42+ import java .util .Collections ;
43+ import java .util .Deque ;
2744import java .util .EnumSet ;
2845import java .util .HashSet ;
46+ import java .util .LinkedHashMap ;
47+ import java .util .List ;
48+ import java .util .Map ;
49+ import java .util .Properties ;
2950import java .util .Set ;
51+ import java .util .function .BiFunction ;
3052import java .util .regex .Matcher ;
3153import java .util .regex .Pattern ;
3254import java .util .stream .Collectors ;
3355import java .util .stream .Stream ;
3456import javax .annotation .Nonnull ;
3557import javax .annotation .Nullable ;
58+ import javax .xml .stream .XMLInputFactory ;
59+ import javax .xml .stream .XMLStreamConstants ;
60+ import javax .xml .stream .XMLStreamException ;
61+ import javax .xml .stream .XMLStreamReader ;
62+
3663import org .slf4j .Logger ;
3764import org .slf4j .LoggerFactory ;
3865
@@ -85,6 +112,9 @@ public class ApplicationModuleImpl extends SinkModuleBase implements Application
85112 JETTY_TEST_APP ));
86113 public static final String WEB_INF = "WEB-INF" ;
87114 public static final String WEB_XML = "web.xml" ;
115+
116+ public static final String TOMCAT_USERS_XML = "tomcat-users.xml" ;
117+ public static final String TOMCAT_SERVER_XML = "server.xml" ;
88118 public static final String WEBLOGIC_XML = "weblogic.xml" ;
89119 public static final String IBM_WEB_EXT_XMI = "ibm-web-ext.xmi" ;
90120 public static final String IBM_WEB_EXT_XML = "ibm-web-ext.xml" ;
@@ -124,7 +154,7 @@ public ApplicationModuleImpl(final Dependencies dependencies) {
124154 * @param realPath the real path of the application
125155 */
126156 @ Override
127- public void onRealPath (final @ Nullable String realPath ) {
157+ public void onRealPath (String serverInfo , final @ Nullable String realPath ) {
128158 if (realPath == null ) {
129159 return ;
130160 }
@@ -133,8 +163,15 @@ public void onRealPath(final @Nullable String realPath) {
133163 return ;
134164 }
135165 final AgentSpan span = AgentTracer .activeSpan ();
166+
167+ if (serverInfo != null ) {
168+ if (serverInfo .contains ("Tomcat" )) {
169+ checkTomcatVulnerabilities (serverInfo , root , span );
170+ }
171+ }
172+
136173 checkInsecureJSPLayout (root , span );
137- checkWebXmlVulnerabilities (root , span );
174+ checkWebXmlVulnerabilities (serverInfo , root , span );
138175 // WEBLOGIC
139176 checkWeblogicVulnerabilities (root , span );
140177 // WEBSPHERE
@@ -161,6 +198,87 @@ public void checkSessionTrackingModes(@Nonnull Set<String> sessionTrackingModes)
161198 new Evidence (SESSION_REWRITING_EVIDENCE_VALUE )));
162199 }
163200
201+ private void checkTomcatVulnerabilities (String serverInfo , @ Nonnull final Path path , final AgentSpan span ) {
202+ String tomcatPath = System .getProperty ("catalina.home" );
203+
204+ RequestContext reqCtx = span .getRequestContext ();
205+ CallbackProvider cbp = AgentTracer .get ().getCallbackProvider (RequestContextSlot .APPSEC );
206+ TriFunction <RequestContext , String , Map <String , ?>, Flow <Void >> callback =
207+ cbp .getCallback (EVENTS .configurationData ());
208+ if (reqCtx == null || callback == null ) {
209+ return ;
210+ }
211+
212+ try {
213+
214+ // Check for default tomcat applications (Manager, Host Manager, etc)
215+ Path tomcatWebappsPath = Paths .get (tomcatPath , "webapps" );
216+
217+ Map <String , Object > webapps = new LinkedHashMap <>();
218+ List <String > appNames = new ArrayList <>();
219+ webapps .put ("webapps" , appNames );
220+ try (DirectoryStream <Path > stream = Files .newDirectoryStream (tomcatWebappsPath , Files ::isDirectory )) {
221+ for (Path p : stream ) {
222+ //System.out.println("Folder: " + p.getFileName());
223+ appNames .add (p .getFileName ().toString ());
224+ }
225+
226+ Flow <Void > flow = callback .apply (reqCtx , serverInfo , webapps );
227+ } catch (IOException e ) {
228+ System .err .println ("Error reading directory: " + e .getMessage ());
229+ }
230+
231+
232+ // Check for Weak credentials in tomcat-users.xml
233+ Path tomcatUsersXmlPath = Paths .get (tomcatPath , "conf" , TOMCAT_USERS_XML );
234+ Map <String , List <Map <String , Object >>> tomcatUsersXmlConfig = getParsedXmlContent (tomcatUsersXmlPath );
235+ if (tomcatUsersXmlConfig != null ) {
236+ List <Map <String , Object >> userTags = tomcatUsersXmlConfig .get ("user" );
237+ if (userTags != null ) {
238+ for (Map <String , Object > userTag : userTags ) {
239+ Flow <Void > flow = callback .apply (reqCtx , serverInfo , userTag );
240+ }
241+ }
242+ }
243+
244+
245+ // Check server.xml
246+ Path serverXmlPath = Paths .get (tomcatPath , "conf" , TOMCAT_SERVER_XML );
247+ Map <String , List <Map <String , Object >>> serverXmlConfig = getParsedXmlContent (serverXmlPath );
248+
249+ if (serverXmlConfig != null ) {
250+ List <Map <String , Object >> valveTags = serverXmlConfig .get ("Valve" );
251+ if (valveTags != null ) {
252+ for (Map <String , Object > valveTag : valveTags ) {
253+ Flow <Void > flow = callback .apply (reqCtx , serverInfo , valveTag );
254+ }
255+ }
256+ List <Map <String , Object >> connectorTags = serverXmlConfig .get ("Connector" );
257+ if (connectorTags != null ) {
258+ for (Map <String , Object > connectorTag : connectorTags ) {
259+ Map <String , Object > serviceTag = new LinkedHashMap <>();
260+ serviceTag .put ("Connector" , connectorTag );
261+ Flow <Void > flow = callback .apply (reqCtx , serverInfo , serviceTag );
262+ }
263+ }
264+ }
265+
266+ // Check jvm properties
267+ Properties properties = System .getProperties ();
268+ Map <String , Object > jvmProperties = new LinkedHashMap <>();
269+ Map <String , Object > propertiesTag = new LinkedHashMap <>();
270+ for (String key : properties .stringPropertyNames ()) {
271+ propertiesTag .put (key , properties .getProperty (key ));
272+ }
273+ jvmProperties .put ("Properties" , propertiesTag );
274+ Flow <Void > flow = callback .apply (reqCtx , serverInfo , jvmProperties );
275+
276+ } catch (Exception e ) {
277+ throw new RuntimeException (e );
278+ }
279+
280+ }
281+
164282 private void checkWebsphereVulnerabilities (@ Nonnull final Path path , final AgentSpan span ) {
165283 checkWebsphereXMLVulnerabilities (path , span );
166284 checkWebsphereXMIVulnerabilities (path , span );
@@ -199,7 +317,109 @@ private void checkWeblogicVulnerabilities(@Nonnull final Path path, final AgentS
199317 }
200318 }
201319
202- private void checkWebXmlVulnerabilities (@ Nonnull final Path path , final AgentSpan span ) {
320+ public static Map <String , List <Map <String , Object >>> parseXmlFromString (String xmlInput ) throws XMLStreamException {
321+ XMLInputFactory factory = XMLInputFactory .newInstance ();
322+ try (InputStream xmlStream = new ByteArrayInputStream (xmlInput .getBytes ())) {
323+ XMLStreamReader reader = factory .createXMLStreamReader (xmlStream );
324+ return parseGroupedElements (reader );
325+ } catch (IOException e ) {
326+ throw new RuntimeException ("Error reading XML input" , e );
327+ }
328+ }
329+
330+ private static Map <String , List <Map <String , Object >>> parseGroupedElements (XMLStreamReader reader ) throws XMLStreamException {
331+ Map <String , List <Map <String , Object >>> groupedMap = new LinkedHashMap <>();
332+
333+ while (reader .hasNext ()) {
334+ int eventType = reader .next ();
335+
336+ switch (eventType ) {
337+ case XMLStreamConstants .START_ELEMENT :
338+ String currentElement = reader .getLocalName ();
339+ Map <String , String > attributes = getAttributes (reader );
340+
341+ // Создаем элемент
342+ Map <String , Object > element = new LinkedHashMap <>(attributes );
343+
344+ // Добавляем элемент в общую структуру
345+ groupedMap .computeIfAbsent (currentElement , k -> new ArrayList <>()).add (element );
346+ break ;
347+
348+ case XMLStreamConstants .END_ELEMENT :
349+ // Просто выходим из цикла на конец элемента
350+ break ;
351+
352+ case XMLStreamConstants .CHARACTERS :
353+ // Игнорируем текст, так как у нас только атрибуты нужны
354+ break ;
355+ }
356+ }
357+ return groupedMap ;
358+ }
359+
360+ private static Map <String , String > getAttributes (XMLStreamReader reader ) {
361+ Map <String , String > attributes = new LinkedHashMap <>();
362+ for (int i = 0 ; i < reader .getAttributeCount (); i ++) {
363+ attributes .put (reader .getAttributeLocalName (i ), reader .getAttributeValue (i ));
364+ }
365+ return attributes ;
366+ }
367+
368+
369+ // public static Map<String, Object> parseXmlFromString(String xmlContent) throws Exception {
370+ // Map<String, Object> result = new LinkedHashMap<>();
371+ // Deque<Map<String, Object>> stack = new ArrayDeque<>();
372+ // stack.push(result);
373+ //
374+ // XMLInputFactory factory = XMLInputFactory.newInstance();
375+ // XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xmlContent));
376+ //
377+ // String currentElement = null;
378+ // while (reader.hasNext()) {
379+ // int event = reader.next();
380+ // switch (event) {
381+ // case XMLStreamConstants.START_ELEMENT:
382+ // currentElement = reader.getLocalName();
383+ // Map<String, Object> newElement = new LinkedHashMap<>();
384+ // stack.peek().merge(currentElement, newElement, (oldValue, newValue) -> {
385+ // if (oldValue instanceof List) {
386+ // ((List<Object>) oldValue).add(newValue);
387+ // return oldValue;
388+ // } else {
389+ // List<Object> list = new ArrayList<>();
390+ // list.add(oldValue);
391+ // list.add(newValue);
392+ // return list;
393+ // }
394+ // });
395+ // stack.push(newElement);
396+ // break;
397+ // case XMLStreamConstants.CHARACTERS:
398+ // if (!reader.isWhiteSpace()) {
399+ // stack.peek().merge(currentElement, reader.getText().trim(), (oldValue, newValue) -> {
400+ // if (oldValue instanceof List) {
401+ // ((List<Object>) oldValue).add(newValue);
402+ // return oldValue;
403+ // } else {
404+ // List<Object> list = new ArrayList<>();
405+ // list.add(oldValue);
406+ // list.add(newValue);
407+ // return list;
408+ // }
409+ // });
410+ // }
411+ // break;
412+ // case XMLStreamConstants.END_ELEMENT:
413+ // stack.pop();
414+ // break;
415+ // }
416+ // }
417+ // return result;
418+ // }
419+
420+
421+ private void checkWebXmlVulnerabilities (final String serverInfo , final Path path , final AgentSpan span ) {
422+
203423 String webXmlContent = getXmlContent (path , WEB_XML );
204424 if (webXmlContent == null ) {
205425 return ;
@@ -377,8 +597,7 @@ private static int getLine(String webXmlContent, int index) {
377597 }
378598
379599 @ Nullable
380- private static String getXmlContent (final Path realPath , final String fileName ) {
381- Path path = realPath .resolve (WEB_INF ).resolve (fileName );
600+ private static String readXmlContent (final Path path ) {
382601 if (Files .exists (path )) {
383602 try {
384603 return new String (Files .readAllBytes (path ), StandardCharsets .UTF_8 );
@@ -389,6 +608,26 @@ private static String getXmlContent(final Path realPath, final String fileName)
389608 return null ;
390609 }
391610
611+ @ Nullable
612+ private static Map <String , List <Map <String , Object >>> getParsedXmlContent (final Path path ) {
613+ String xmlContent = readXmlContent (path );
614+ if (xmlContent == null ) {
615+ return null ;
616+ }
617+ try {
618+ return parseXmlFromString (xmlContent );
619+ } catch (Exception e ) {
620+ LOGGER .debug (SEND_TELEMETRY , "Failed to parse {}" , path , e );
621+ }
622+ return null ;
623+ }
624+
625+ @ Nullable
626+ private static String getXmlContent (final Path realPath , final String fileName ) {
627+ Path path = realPath .resolve (WEB_INF ).resolve (fileName );
628+ return readXmlContent (path );
629+ }
630+
392631 private static Collection <Path > findInsecureJspPaths (final Path root ) {
393632 try {
394633 final InsecureJspFolderVisitor visitor = new InsecureJspFolderVisitor ();
0 commit comments