Web


Nmap discovered a web server on the target port 80 The running service is nginx 1.18.0

Webroot It appears to provide a web hosting service

Wappalyzer identified technologies involved

The passive crawler from Burp Suite picked up an interesting endpoint; login

/login


Several methods of authentication bypass were attempted with no avail

Fuzzing


┌──(kali㉿kali)-[~/archive/htb/labs/cozyhosting]
└─$ ffuf -c -w /usr/share/wordlists/seclists/discovery/web-content/directory-list-2.3-medium.txt -u http://cozyhosting.htb/FUZZ -ic 
________________________________________________
 
 :: Method           : GET
 :: URL              : http://cozyhosting.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________
 
[status: 200, Size: 4431, Words: 1718, Lines: 97, Duration: 95ms]
    * fuzz: login
 
[status: 200, Size: 12706, Words: 4263, Lines: 285, Duration: 139ms]
    * fuzz: index
 
[status: 401, Size: 97, Words: 1, Lines: 1, Duration: 59ms]
    * fuzz: admin
 
[status: 204, Size: 0, Words: 1, Lines: 1, Duration: 32ms]
    * fuzz: logout
 
[status: 500, Size: 73, Words: 1, Lines: 1, Duration: 36ms]
    * fuzz: error
 
:: Progress: [220547/220547] :: Job [1/1] :: 307 req/sec :: Duration: [0:14:19] :: Errors: 0 ::

Found /admin

┌──(kali㉿kali)-[~/archive/htb/labs/cozyhosting]
└─$ ffuf -c -w /usr/share/wordlists/seclists/discovery/web-content/spring-boot.txt -u http://cozyhosting.htb/FUZZ -ic  
________________________________________________
 
 :: Method           : GET
 :: URL              : http://cozyhosting.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/spring-boot.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________
 
[status: 200, Size: 634, Words: 1, Lines: 1, Duration: 103ms]
    * fuzz: actuator
 
[status: 200, Size: 148, Words: 1, Lines: 1, Duration: 167ms]
    * fuzz: actuator/sessions
 
[status: 200, Size: 487, Words: 13, Lines: 1, Duration: 231ms]
    * fuzz: actuator/env/lang
 
[status: 200, Size: 487, Words: 13, Lines: 1, Duration: 211ms]
    * fuzz: actuator/env/path
 
[status: 200, Size: 487, Words: 13, Lines: 1, Duration: 284ms]
    * fuzz: actuator/env/home
 
[status: 200, Size: 4957, Words: 120, Lines: 1, Duration: 295ms]
    * fuzz: actuator/env
 
[status: 200, Size: 15, Words: 1, Lines: 1, Duration: 232ms]
    * fuzz: actuator/health
 
[status: 200, Size: 9938, Words: 108, Lines: 1, Duration: 276ms]
    * fuzz: actuator/mappings
 
[status: 200, Size: 127224, Words: 542, Lines: 1, Duration: 325ms]
    * fuzz: actuator/beans
 
:: Progress: [112/112] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

Performing additional fuzzing with a different wordlist returned a directory; /actuator

the /actuator directory is mostly associated with spring boot framework, which is used in Spring web application

In a Spring Boot web application, an actuator is a set of production-ready features that provide valuable insights and management capabilities. These features include endpoints that expose information about the application’s health, metrics, environment, and more. Actuators are crucial for monitoring and managing the application in a production environment, as they enable administrators to check its status, diagnose issues, and even modify certain aspects of the application without stopping it. Spring Boot Actuators can be easily integrated into an application by adding the necessary dependencies and configuring them to expose the desired endpoints, making it a powerful tool for ensuring the reliability and performance of Spring Boot applications.

/actuator


Heading over to the /actuator page reveals a series of API endpoints Location reveals that the web application itself it hosted over localhost:8080 and likely proxied through nginx

/actuator/health


The /actuator/health endpoint shows the status of the web application

/actuator/env


The /actuator/env endpoint in a Spring Boot application’s Actuator module provides information about the application’s environment properties and configuration. It exposes a snapshot of the application’s runtime environment, including properties from configuration files, environment variables, and other sources. Developers and administrators can use this endpoint to inspect the current configuration, which can be valuable for troubleshooting and monitoring purposes in real-time.

/actuator/mappings


┌──(kali㉿kali)-[~/archive/htb/labs/cozyhosting]
└─$ curl http://cozyhosting.htb/actuator/mappings  
{"contexts":{"application":{"mappings":{"dispatcherServlets":{"dispatcherServlet":[{"handler":"Actuator web endpoint 'env'","predicate":"{GET [/actuator/env], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/env"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"Actuator web endpoint 'env-toMatch'","predicate":"{GET [/actuator/env/{toMatch}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/env/{toMatch}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"Actuator web endpoint 'sessions'","predicate":"{GET [/actuator/sessions], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/sessions"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"Actuator web endpoint 'health'","predicate":"{GET [/actuator/health], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/health"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"Actuator web endpoint 'health-path'","predicate":"{GET [/actuator/health/**], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/health/**"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"Actuator web endpoint 'mappings'","predicate":"{GET [/actuator/mappings], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/mappings"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"Actuator web endpoint 'beans'","predicate":"{GET [/actuator/beans], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/beans"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"Actuator root web endpoint","predicate":"{GET [/actuator], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcLinksHandler","name":"links","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljakarta/servlet/http/HttpServletResponse;)Ljava/util/Map;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"handler":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)","predicate":"{ [/error]}","details":{"handlerMethod":{"className":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController","name":"error","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;)Lorg/springframework/http/ResponseEntity;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":[],"params":[],"patterns":["/error"],"produces":[]}}},{"handler":"htb.cloudhosting.compliance.ComplianceService#executeOverSsh(String, String, HttpServletResponse)","predicate":"{POST [/executessh]}","details":{"handlerMethod":{"className":"htb.cloudhosting.compliance.ComplianceService","name":"executeOverSsh","descriptor":"(Ljava/lang/String;Ljava/lang/String;Ljakarta/servlet/http/HttpServletResponse;)V"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/executessh"],"produces":[]}}},{"handler":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)","predicate":"{ [/error], produces [text/html]}","details":{"handlerMethod":{"className":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController","name":"errorHtml","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljakarta/servlet/http/HttpServletResponse;)Lorg/springframework/web/servlet/ModelAndView;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":[],"params":[],"patterns":["/error"],"produces":[{"mediaType":"text/html","negated":false}]}}},{"handler":"ParameterizableViewController [view=\"admin\"]","predicate":"/admin"},{"handler":"ParameterizableViewController [view=\"addhost\"]","predicate":"/addhost"},{"handler":"ParameterizableViewController [view=\"index\"]","predicate":"/index"},{"handler":"ParameterizableViewController [view=\"login\"]","predicate":"/login"},{"handler":"ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/]]","predicate":"/webjars/**"},{"handler":"ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]","predicate":"/**"}]},"servletFilters":[{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"requestContextFilter","className":"org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"Tomcat WebSocket (JSR356) Filter","className":"org.apache.tomcat.websocket.server.WsFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"serverHttpObservationFilter","className":"org.springframework.web.filter.ServerHttpObservationFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"characterEncodingFilter","className":"org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"springSecurityFilterChain","className":"org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"formContentFilter","className":"org.springframework.boot.web.servlet.filter.OrderedFormContentFilter"}],"servlets":[{"mappings":["/"],"name":"dispatcherServlet","className":"org.springframework.web.servlet.DispatcherServlet"}]}}}}                                                                                                                  

the /actuator/mappings endpoint in a spring boot application’s actuator module provides a comprehensive list of all the available HTTP request mappings in the application. It includes details about the mapping patterns, HTTP methods, controller methods, and more. This endpoint is useful for developers and administrators to understand how incoming requests are mapped to specific controller methods, making it easier to debug and analyze the application’s request handling process.

/actuator/sessions


According to the official documentation, The sessions endpoint provides information about the application’s HTTP sessions that are managed by Spring Session The /actuator/sessions endpoint reveals 2 active sessions with what appears to be session IDs While the UNAUTHORIZED user is likely mine, the kanderson user appears to be valid

I will grab the session ID of the kanderson user and attempt to hijack it

Session Hijacking

Authenticated to the /admin page

the /admin page reveals that the web application is cozy cloud

cozy is a personal cloud platform free, and self-hostable, written in Go (the former version, v2, was written in Node.js instead).

Automatic Patching

This part in particular seems to be the way in as it appears to interact with SSH service While it says automatic patching, it is unclear what exactly it does to achieve that I will test it out to clarify the feature

It sends out a POST request to the /executessh endpoint

The web app returned an error that the host was not added Additionally, there is the familiar SSH error, which suggests that it might actually be performing OS command to SSH to the requested host While it is clear that I cannot SSH to Kali since the SSH file isn’t included in the target’s .ssh/authorized_keys file, I may be able to perform OS command injection