Some weeks ago now, my good friend Mikbrosim, and I were sitting a sunday evening looking for something we could hack. After searching the internet for a while, I found some really old looking site. The site had some webcam, of what looked like a private backyard (publicly exposed of course), and some sort of weather dashboard. Looking around on the site a bit, it seemed really odd, and broken; however nothing was to be found, and we obviously didn’t want to pentest something we didn’t have permissions to. Suddenly I found at the bottom a “Template by PWS_Dashboard”, link that linked directly to the source code! Score! So we quickly downloaded the source down, and started having a look.

Stage 1. Recon

So firstly we wanted to find out, what this actually was, and who used it? It seemed to be a “Personal Weather Station Dashboard”, used by hobby meteorologists, or just people that wanted to get information about weather near them. This would be done by having your own weatherstation, and then you could upload to the dashboard and see the data, it’s probably also possible to use some other peoples data, and just display this. Regardless, the weatherstations are very expensive, wouldn’t be good if people came in and totally bricked them by running shady code on your server.

Amazon

The site had the following text

Downloads: Click here to download the latest full version .
All published updates up to **March 28, 2022** are included in the download.
New in this update: Multi (4) language setup and compatabillity tested with PHP 8.1.4

That lead us to believe, that even though this software was very old-looking, it was still being used, and maintained by some hobby developers. They even have a forum over at https://www.weather-watch.com/smf/index.php/board,77.0.html which is pretty active. It wasn’t possible for us to get a good estimate, of how many servers were running this software, shodan only gave us a handful, but a simple google dork, yielded in the ballpark of around 60-70 users. Now imagine if every one of these users were running a setup with a custom camera and a 200$ weather station. That’s a lot of damage (Potentially 12k USD). While that’s interesting and all, you’re probably here for the technical write-up, not these shenanigans, so let’s dive in.

Stage 2. Code analysis

We’d like to say that we did something extremely smart, and that this was a very difficult vulnerability to find, but it was a giant pool of poor authentication, and bad access management. Users got to have a bit too much fun on other peoples sites. We opened it up, and to our surprise (not) it was PHP. With a bit to look through. Unzipped

Now we went ahead and tried getting a local environment to work, so that we had something to test on. To do this we ran a few commands, this was kind of dependent on which dependencies we were missing individually, specifically I ran:

sudo apt install php-curl
sudo apt install php-simplexml
php -S localhost:8899

and the site was running on my localhost. Upon visiting the URL we saw, that the setup process had now begun, along with some information about what dependencies were missing, this is how we found out which packages to install. We got it working reasonably quickly, so there was no reason for us to set up a docker instance. Following the setup manual, we find that the default password is set as 12345, however it’s quite explicitly stated, that this is a weak password, and that it should be changed. For the sake of the write-up, we change the password to 0dayR3S4RCH!. This will probably be important later;) Now we want to find out which attack surfaces there are, this could be input fields, places with database interactions, or GET and POST request handling. We started by just playing a bit around with the website, seeing if we can find something odd.

Page

Vulnerability 1 - Bad hash

At the endpoint http://localhost:8899/PWS_easyweathersetup.php we have a log-in page, now we could of-course try just enumerating weak PWS_Dashboard servers and trying the default password here, and it’s probably not unrealistic, that we’ll get through on atleast one poorly configured server, but that’s not fun. The log-in part of this endpoint looks like:

if (isset($_POST['submit_pwd']))
     {  if (isset($_POST['passwd']) ) {$pass = $_POST['passwd']; } else {$pass = '';} 
        if ($pass != $password) 
             {  showForm('<b style="color: red;">A valid password needs to be entered</b>');   
                exit;}
        } 
elseif (!isset($_POST['submit']) )
     {  showForm('PWS_Dashboard setup (version 2012_lts)'); 
        exit; }
if (isset($_REQUEST['lang']) )
     {  $setup_lang = substr(trim($_REQUEST['lang']).'  ',0,2);}
else {  $setup_lang = 'en';}

We see that if the POST parameter submit_pwd is set, then it checks for the parameter passwd, it wants this pass to be equal to password, which is the correct password. Now we’re not really PHP programmers, so we couldn’t see anything too horrendous about this. We however now have a few pieces of interesting information, we know that the programmer used submit_pwd, for submitting log-ins and passwd for password parameters. We try grepping for these.

submit_pwd_files

We have four files with submit_pwd, looking through, we quickly see something that should probably not have been added to the code.

# PWS_winter.php - omitted for brevity
if (isset($_POST['submit_pwd']))
     {  if (isset($_REQUEST['passwd']) ) {$pass = $_REQUEST['passwd']; } else {$pass = '';}
        if ($pass != $password && !password_verify($pass, '$2y$10$S1K2rXeaAihG2Ro2lBxh2e7UfMXht3RkocukvxKRzDFXqx4dJND5i') )
             {  echo $html;
                showForm('<b style="color: red;">We need a valid password</b>');
                exit;}
        }

We see that it checks if we’re trying to submit password, it first checks if our parameter matches the password, set originally, and then it checks if your password matches the hash that obviously has been hardcoded into the code. This means that if we manage to bruteforce this hash and insert that as a request we’d have a line that says:

if (true && !true){ exit; }

This will never exit. If we manage to get the plaintext value for the hash, we’ll be allowed in to the PWS_winter.php site, but why is that interesting? Well for starters we have a line that says:

<input type="hidden" style="padding: 0px; border: 0px; margin: 0px;" name="passwd" value="'.$password.'">';

And the $password parameter hasn’t been overwritten. For some reason this is on the site? Logging in would leak the real password. We originally tried bruteforcing it, but then we thought, meh it’s probably in rockyou.txt.

We figured out it was a bcrypt hash, which we also could find out by running the hashid command on the file containing the hash. Then we ran the corresponding hashcat command. hashcat -m 3200 -a 0 hash rockyou.txt.

After just 8 minutes on a quite weak Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz we have the password, which is the password support

$2y$10$S1K2rXeaAihG2Ro2lBxh2e7UfMXht3RkocukvxKRzDFXqx4dJND5i:support

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2y$10$S1K2rXeaAihG2Ro2lBxh2e7UfMXht3RkocukvxKRzDFX...dJND5i
Time.Started.....: Tue Nov  8 17:09:10 2022 (8 mins, 0 secs)
Time.Estimated...: Tue Nov  8 17:17:10 2022 (0 secs)

And truth be told, logging into the PWS_winter.php site, yields the password. Password Leak

This is a pretty good find, and it’s not depicted anywhere in the installation guide or documentation, that this should be changed. Meaning this will be default on all instances of PWS_Dashboard, unless someone found this themselves, changed it, and never bothered telling anyone. That’s one vulnerability. A few to go.


Vulnerability 2 - Limited file read

Another interesting vulnerability is located in the endpoint /PWS_listfile.php

if (isset ($_REQUEST['file'])) {$file   = trim($_REQUEST['file']);}
if (isset ($_REQUEST['type']))
     {  $in     = trim($_REQUEST['type']);
        if (in_array ($in,$types) ) {$type = $in;} }
if (strpos($file,'settings.php') || strpos($file,'twitter_keys.php'))
     {  if (!array_key_exists('pw',$_REQUEST) ) {die ('Security error');}
        include 'PWS_settings.php';
        $pw     = trim($_REQUEST['pw']);
        if ($pw <> $password && !password_verify($pw, '$2y$10$S1K2rXeaAihG2Ro2lBxh2e7UfMXht3RkocukvxKRzDFXqx4dJND5i')) {die ('Security error');}
} // check validity request
$string = file_get_contents ($file);

This takes a file parameter in the GET request, and then it takes a file, we can’t read the twitter_keys.php file or the settings.php file, unless we give the hashed password, support. Then we can. This will again leak the real password.

Leak_Curl

However without the password, the user can read all files on the system, that the process can. That means leaking /etc/passwd, and all the other very interesting files. Keep in mind that this is not LFI, because it’s file_get_contents, which doesn’t include anything, simply reads the file, so this would be classified as directory traversal. That’s the second vulnerability, onto the third.


Vulnerability 3 - Full file read (pt. 1)

A couple of days after we initially finished looking at the library, Mikbrosim found another file read, in the endpoint called /PWS_frame_text.php:

if (isset ($_REQUEST['showtext']) && $_REQUEST['showtext'] <> '' )
     {  $link   = trim($_REQUEST['showtext']); }

if (isset ($_REQUEST['type']) && $_REQUEST['type'] <> '' )
     {  $param  = trim($_REQUEST['type']);
        if ($param <> 'url') {$param = 'file';}
        $type   = $param;}

if ($type == 'url')    #     'https://tgftp.nws.noaa.gov/data/raw/cd/cdus41.klwx.cli.bwi.txt';
      { $ch             = curl_init();
        curl_setopt($ch, CURLOPT_URL,$link);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,10); // connection timeout
        curl_setopt($ch, CURLOPT_TIMEOUT,20);        // data timeout 30 seconds
        $result = curl_exec ($ch);
        $info   = curl_getinfo($ch);
        $error  = curl_error($ch);
        curl_close ($ch);    }
else {  $result         = file_get_contents ( $link);}

We can use this to arbitrary file read, because this doesn’t check if we’ve set a password.

No_Unauth_LFI

Vulnerability 4 - Full File Read (pt. 2)

We found another full file read in the PWS_printfile.php endpoint, that just allows for printing arbitrary files.

if (isset ($_REQUEST['file']) )  // we need to now the link to the file
     {  $file   = trim($_REQUEST['file']);}
else {  $file   = $livedata;}
#
if (!file_exists ($file) )
     {  echo __LINE__.' no file '.$file.' found'; return;}
else {  $data = file_get_contents ($file);
        if ($data  == false)
             {  echo __LINE__.' no file or empty file '.$file.' found'; return;}
}   // eo check file exists and is usable
#
if (isset ($_REQUEST['showall']) )
     {  $used_only = false; }

This means we can give it a parameter, file, which just will read the file we give it, eg. localhost:8899/PWS_printfile.php?file=/etc/passwd

Vulnerability 5 - Remote Code Execution

Now we want to take this, and turn it into something more powerful. Quite quickly we see that the settings.php endpoint is vulnerable. We can also edit this file, because we’re now admin, and we can now change the settings. The reason that we can inject PHP directly into this file, is because there’s no input parsing:

       $stationlocation = "A city in Belgium";
                 $email = "someone@dot.com";
               $twitter = "pwsweather";
              $facebook = "pwsweather";
        $solve_problem1 = "not_used";
        $solve_problem2 = "not_used";
                    $TZ = "Europe/Brussels";

It’s rather trivial to get code execution here, by just simply changing the settings, our input will be injected directly into the settings.php file: RCE_101

We start (Yellow) by adding a double-quote, and a semi-colon, this is done to end the line in proper PHP fashion, now we can just execute some arbitrary PHP command (Blue), and to end the line (Red), we add a hashtag, because this is how you comment in PHP. We can see this does infact, execute remote code. RCE_in_action

And quite quickly one can get proper full fletched remote code execution. Which could allow us to take control of servers running this software, and run commands on their systems. This could lead to backdoors, ransomware attacks etc. ls_lah

To visualize, these are sites, I assume to be vulnerable (of course without testing, but looking at the software version). (57 servers). vuln_

This is quite bad.

Stage 3. Responsible disclosure

We started by filing a CVE request to MITRE. In which we disclosed all of the vulnerabilities above. Then we sent mr. Kuil, the main developer of PWS_Dashboard a mail, revolving these vulnerabilities, with some advice on how these could be fixed, along with the same technical description we sent mitre, so that he could reproduce, and check that these issues are indeed present.

What can you do, to fix this?
First, let's start by acknowledging, that it's simply not secure to have this "support" password. Even changing it for a stronger password, will at some point, theoretically allow for access to the panel. Best bet, would be to remove this part of the code, and only rely on the password set by the configurer.

The password currently in the settings, set by the configurer, should also not be shown in plaintext. (...)

You cannot have the function PWS_frame_text.php, show text for files without authentication, this also allows for information disclosure. Same applies for PWS_listfile.php

In the settings page, the following PHP could be applied to all fields, to input sanitize. However some alternative method should probably be applied to the password, as to avoid weak passwords being used. Something like this might help input sanitize, however it's probably not perfect, and just an example:

$result = preg_replace("/[^a-zA-Z0-9]+/", "", $s);
(...)

We created a mini-timeline:

Timeline

Conclusion, we managed to get the issue largely fixed, within a day, and the maintainer was very professional in handling the issue. Our correspondance was mainly messages through the forum for PWS_Dashboard, where we told him what to fix, and gave him concrete advices on how to do so.