The Incredibly Insecure Weather Station

I recently purchased this thing on a whim – ECOWITT GW1102 Home Weather Station. It’s exactly what it sounds like – a mini weather station for your house. It has all the usual sensors you’d except a weather station to have, and I’m actually very pleased with the hardware, considering the cheap price.

However, it is missing one thing – software security. But really, what did I expect from a cheap, home weather station?

Comically, the landing page of the weather station’s server gives an illusion of some sort of security.

Password goes here.

Let’s intercept a request of us logging in.

Don’t steal my password.

This is all over HTTP. We post our password to /set_login_info – which seems like an odd endpoint for logging in. Notice the response does not set any cookies or seem like it actually does any sort of verification. Hmmm.

Anyway, after logging in, we are directed to /liveData.html. This page does exactly what its name implies. But let’s look at the links on the side of the page – particularly the Local Network link.

Click the Local Network link on the left-hand side.

If we intercept the requests in Burp after we click the Local Network link, we see a call to a /get_network_info endpoint. This returns info about the WiFi network to which the weather station is connected.

That’s my WiFi SSID and password.

Interesting. Notice again that there appears to be no authentication going on with this request. Let’s try to curl this endpoint

Uh oh.

Or how about the device password (not that you actually need the password now).

The password is now Weather24689 because I changed it without being authorized.

You can also do fun things like reboot the station, or get the user’s external weather reporting site’s API keys, etc. I notified ECOWITT support, but I’m assuming this won’t be fixed any time soon.

I did find some of these exposed to the internet, but I’d probably avoid that, if I were you. With that said, I actually like the hardware. It’s fun to play around with, and it is inexpensive.

Deploying and Configuring a Bug Bounty Box with Terraform and Ansible

Prerequisites and Getting Started

I sometimes like to spin up a virutal machine in the cloud, do some testing, and then tear it down. It doesn’t even have to be for bug bounty hunting, but since I’ve been hunting so sporadically lately, that’s what I’ve been using this project for.

Anyway, it becomes tedious to do this repeatedly, so I decided to automate a large majority of the infrastructure creation and configuration with Terraform and Ansible.

In the following article, I’ll deploy a node on Linode, my VPS provider of choice. Use this referral link for a $100, 60-day credit. That way, you can test this project out until you’re blue in the face. The node size I deploy in this post runs $10 a month.

While Terraform and Ansible can both accomplish the same things, they both have their wheel houses. Terraform should be used for deploying infrastructure and Ansible should be used to configure that infrastructure.

In order to follow along with this article, you’ll need to install Terraform and Ansible per your Operating System’s documentation. I’m using Ubuntu 20.10.

Let’s begin b creating a directory structure for your project.

mkdir -p ./bugbounty/{/terraform/templates,ansible}

Next, you’ll need to obtain credentials from Linode. If you haven’t already, create an account, then click on your account name in the top, right-hand corner and select “API Tokens.”

Select create an access token and give it a name. Select Linodes and Read/Write, and then click “Create Token.”

Linode Read/Write Access Token

The token will be a long string of characters. Save this token for usage in a bit!

Terraform

cd into the Terraform directory you just created and create the following files:

$ touch {main.tf,output.tf,variables.tf,variables.tfvars}

The main.tf file is where the magic is done. This file will create the VM to our specifications. The variables.tf file declares variables that are used in main.tf. variables.tfvars will have the initializing values for these variables. You can also initialize the variables directly in variables.tf or even on the command line, if you’d prefer. We do it this way because it makes updating variables slightly easier and our project simpler, in a sense. output.tf defines what values will be printed to the console after we run the project.

Next, create some templates within the templates directory.

touch {./templates/ansible.tmpl,./templates/playbook.tmpl,./templates/hosts}
main.tf

Copy the following code into main.tf:

terraform {
  required_providers {
    linode = {
      source  = "linode/linode"
      version = "1.27.0"
    }
  }
}

# Configure the Linode Provider
provider "linode" {
  token = var.token
}

# Create a Linode
resource "linode_instance" "bugbountybox" {
  image     = var.image
  label     = var.label
  region    = var.region
  type      = var.type
  root_pass = var.root_pass
}

# Create an Ansible playbook from a template file
resource "local_file" "bugbountybox_setup" {
  content = templatefile("./templates/playbook.tmpl",
    {
      ip_address = linode_instance.bugbountybox.ip_address
    }
  )
  file_permission = "0640"
  filename        = "../ansible/playbook.yml"
}

# Create an Ansible config from a template file. 
resource "local_file" "ansible_config" {
  content = templatefile("./templates/ansible.tmpl",
    {
      remote_user = "root"
    }
  )
  file_permission = "0640"
  filename        = "../ansible/ansible.cfg"
}

# Create an Ansible playbook from a template file
resource "local_file" "ansible_inventory" {
  content         = linode_instance.bugbountybox.ip_address
  file_permission = "0640"
  filename        = "../ansible/hosts"
}
variables.tf

Copy the following code into variables.tf:

variable "token" {
  type        = string
  description = "Linode APIv4 token."
  sensitive   = true
}

variable "image" {
  type        = string
  description = "Image to use for your VM."
  default     = "linode/ubuntu20.04"
}

variable "label" {
  type        = string
  description = "Label to give your VM."
}

variable "region" {
  type        = string
  description = "Region where the VM will be created."
}

variable "root_pass" {
  type        = string
  description = "Password for the root account on this VM."
  sensitive   = true
}

variable "type" {
  description = "Your Linode's plan type."
  # You can initialize variables here instead of the tfvars file. 
  default = "g6-standard-1"
}
variables.tfvars

Copy the following code into variables.tfvars, and enter the values as needed:

token     = "" # put your API token here. 
image     = "linode/ubuntu20.04"
label     = "bug-bounty-box"
region    = "us-east"
root_pass = "" # put your new VM's password here. 
output.tf

Copy the following code into output.tf:

output "IP_Address" {
  value = linode_instance.bugbountybox.ip_address
}

Templates

The templates will be used by Terraform to create files that Ansible will use. We could manually create/edit these Ansible files, but why do things manually when we can automate it?

Copy the following code into ansible.tmpl:

[defaults]
host_key_checking = False
remote_user = ${ remote_user }
ask_pass      = True

Copy the following code into playbook.tmpl:

---
- name: Update/upgrade and install packages on remote server.
  hosts: ${ ip_address }
  become: true
  tasks:
    - name: Update
      apt: update_cache=yes force_apt_get=yes cache_valid_time=3600

    - name: Upgrade all packages on servers
      apt: upgrade=dist force_apt_get=yes

    - name: Install packages
      apt:
        pkg:
          - ca-certificates
          - curl
          - apt-transport-https
          - lsb-release
          - gnupg
          - software-properties-common
          - python3-pip
          - unzip
          - tar
          - tmux
          - gobuster
          - wireguard
          - wireguard-tools
          - john
          - hashcat
          - nikto
          - ruby-full
          - ruby-railties
          - hydra
          - cewl
          - whois
          - squid
          - nmap
          - git
          - python3-impacket

        update_cache: true

    - name: Install Golang
      shell: |
        wget https://go.dev/dl/go1.18.linux-amd64.tar.gz
        tar -xvf go1.18.linux-amd64.tar.gz
        chown -R root:root ./go
        mv go /usr/local
        echo "export GOPATH=$HOME/go" >> $HOME/.bashrc
        echo "export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin" >> $HOME/.bashrc
      args:
        executable: /bin/bash

    - name: Install Amass
      shell: |
        curl -s https://api.github.com/repos/OWASP/Amass/releases/latest | grep "browser_download_url.*linux_amd64.zip" | cut -d : -f 2,3 | tr -d \" | wget -i -
        unzip amass* 
        chmod +x ./amass_linux_amd64/amass 
        mv ./amass_linux_amd64/amass /usr/bin/
      args:
        executable: /bin/bash

    - name: Install Nuclei
      shell: |
        curl -s https://api.github.com/repos/projectdiscovery/nuclei/releases/latest | grep "browser_download_url.*linux_amd64.zip" | cut -d : -f 2,3 | tr -d \" | wget -i -
        unzip nuclei* nuclei
        chmod +x nuclei
        mv nuclei /usr/bin/
      args:
        executable: /bin/bash

    - name: Install FFUF
      shell: |
        curl -s https://api.github.com/repos/ffuf/ffuf/releases/latest | grep "browser_download_url.*linux_amd64.tar.gz" | cut -d : -f 2,3 | tr -d \" | wget -i -
        tar xzf ffuf* ffuf
        chmod +x ffuf
        mv ffuf /usr/bin/
      args:
        executable: /bin/bash

    - name: Install Subfinder
      shell: |
        curl -s https://api.github.com/repos/projectdiscovery/subfinder/releases/latest | grep "browser_download_url.*linux_amd64.zip" | cut -d : -f 2,3 | tr -d \" | wget -i -
        unzip subfinder* subfinder
        chmod +x subfinder
        mv subfinder /usr/bin/
      args:
        executable: /bin/bash

    - name: Install Aquatone
      shell: |
        curl -s https://api.github.com/repos/michenriksen/aquatone/releases/latest | grep "browser_download_url.*linux_amd64-*" | cut -d : -f 2,3 | tr -d \" | wget -i -
        unzip aquatone* aquatone
        chmod +x aquatone 
        mv aquatone /usr/bin
      args:
        executable: /bin/bash

    - name: Install getallurls (gau)
      shell: |
        curl -s https://api.github.com/repos/lc/gau/releases/latest | grep "browser_download_url.*linux_amd64.tar.gz" | cut -d : -f 2,3 | tr -d \" | wget -i -
        tar xzf gau* gau 
        chmod +x gau 
        mv gau /usr/bin
      args:
        executable: /bin/bash

    - name: Install CrackMapExec
      shell: |
        wget https://github.com/byt3bl33d3r/CrackMapExec/releases/download/v5.2.2/cme-ubuntu-latest.zip
        unzip cme-ubuntu-latest.zip -d "$HOME/tools/*"
        pip3 install cffi==1.14.5
      args:
        executable: /bin/bash

    - name: Reboot the box
      reboot:
        msg: "Reboot initiated by Ansible for updates"
        connect_timeout: 5
        reboot_timeout: 300
        pre_reboot_delay: 0
        post_reboot_delay: 30
        test_command: uptime

If you take a close look at these templates, you’ll see variables indicated with the following templating syntax:

${ variable_name }

These are “filled in” during the terraform apply process. We only have a single variable in each of these files, but you can use as many as you’d like depending on what you’re trying to accomplish. This is a very powerful feature. It allows you to dynamically create files to be used in other processes – in our case, Ansible.

It’s Alive!

We are ready to create our infrastructure by running the following commands within the terraform directory. Type “yes” when prompted after the apply command.

$ terraform init

$ terraform fmt

$ terraform validate

$ terraform apply -var-file="./variables.tfvars"

The terraform init command initializes the project directory. terraform fmt formats the files to the canonical style. terraform validate validates the project to ensure it will work properly. Finally, terraform apply creates your infrastructure using the tfvars file you specified.

If everything goes as planned, you should see output similar to this.

terraform apply output

As you can see, the IP address of our VM was present in the output as we specified in outputs.tf.

Ansible

During the infrastructure creation process, several files should have been created in the ansible directory. Ansible will use these files update/upgrade and install packages on our VM. From the ansible directory we run the following command to configure our new VM. At the start, you will be prompted for the SSH password that you used in your tfvars file.

$ ansible-playbook -i hosts playbook.yml

We need to specify the hosts file that Terraform created so Ansible doesn’t use the hosts file located in /etc/ansible.

This process will take a few minutes to complete, but if all went as planned, you should see something similar to this on your terminal.

Tear it Down

When you are all done playing around with your new VM, you can destroy it with the following command. Please remember to destroy it or else you will incur costs. Type “yes” when prompted.

$ terraform destroy -var-file="./variables.tfvars"

What’s Next?

Now, play around with the above project. Can you set it up to deploy multiple VMs? Can you set it up to deploy multiple VMs, install some other packages, run some commands and send the output of those commands to a database somewhere? Can you set this up on multiple clouds?

The example here is pretty basic, and doesn’t necessarily follow best practices (especially with Ansible), but it gives you the idea of what can be done with automation. Some, if not all, of the leading bug bounty hunters are at least partially automating their work. You should automate too.

Feel free to download all this code from my github and don’t forget to use my link to sign up for a Linode account.

Links

Here are some links to more information and documentation that is pertinent to this article, including a link to this code on Github.

https://www.github.com/pizza-power/bugbountyboxautomation

https://www.terraform.io/cli

https://www.linode.com/docs/guides/how-to-build-your-infrastructure-using-terraform-and-linode/

https://registry.terraform.io/providers/linode/linode/latest/docs

MotionEye Config Info Disclosure

Edit: This was given CVE-2022-25568. As mentioned in my previous posts here and here, I’ve done a little digging into the conditions that are required for the MotioneEye config file to be world viewable, and I’ve reached this conclusion:

As long as a “user” password is not set, the config file will be world readable. Even if an “admin” password has been set, the /config/list file will still be readable by everybody. So, while someone could think they are doing the correct thing by creating a password for the admin user, they may still be leaking private information. Here is a innocuous example from a live instance:

As you can see in this picture, IP addresses/services/passwords are exposed. This is a rather innocuous example, being that it is an internal IP address, but it illustrates how this could be an issue. Imagine if those were your public FTP server credentials. Or if they were your gmail credentials for smtp notifications. The list goes on.

Along with usernames, passwords, auth keys, and email addresses, these config files also contain less sensitive information like internal network IP addresses and URLs, drive and mounting information.

In many ways this vulnerability may be worse that the MotionEye RCE vulnerability that I reported and received a CVE for. In that case, the admin password needed to be left blank (or easily guessed) for someone to get into the admin panel and achieve RCE. In this case, a user could think they’re being secure by setting an admin password, but they leave the user password blank – and the config remains viewable.

I’ve found gmail, gdrive, ftp, sftp, telegram stuff (not sure how auth works there), etc. all exposed to the WWW in these files.

I’ve submitted an issue on the MotionEye github page, but if it is anything like last time, they don’t plan on fixing it/see it as a non-issue.

Edit: The issue was closed before I even finished this post.

Edit: The issue was reopened and I submitted a pull request to fix the issue, although my fix was not tested much, so it may not work properly.

Offensive Security PEN-300 Evasion Techniques and Breaching Defenses – Course and Exam Review

You know, OffSec describes the OSEP as: “Evasion Techniques and Breaching Defenses (PEN-300) is an advanced penetration testing course”. I don’t know how advanced it is, if I can pass, lol. I generally have no idea what I’m doing.

Anyway, I really liked the course. There is a lot of material to keep you busy. Unless you’re already familiar with a large chunk of the topics, you’re probably best-served by purchasing the 90 day version of the course. The challenge labs are fun. Make sure you do them before the exam.

The exam was challenging, but fair. You should be able to figure out what you need to do next somewhat quickly, but executing it may be a different story, if you’re anything like me. Just ask yourself, “What did I just accomplish, and what does that allow me to do now?” If you’ve completed the challenge labs, you will be well-prepared for the exam. Some people say to make sure you do all the questions and extra miles in the lab manual, but I only did, I don’t know, 30% of them?

I don’t know what’s next for me. I have a voucher to do the OSED, but I’m a little burned out at this point. I’ll probably put that off until the summer – because who doesn’t like sitting inside and writing exploits when the weather is nice?

Update: Hacking MotionEye – CVE-2021-44255

I was given CVE-2021-44255 for this – authenticated RCE via a malicious tasks (python pickle) file. So that’s fun. Even though it is authenticated, the default username is admin and the default password is blank, so you know how these things go. I actually haven’t heard of any MotionEye instances being used in botnets or anything.

I should probably request a CVE for the unauthenticated information disclosure that I found, but I need to do some more research on that one.

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44255

https://nvd.nist.gov/vuln/detail/CVE-2021-44255

Sharpshooter, Python2.7, and Pip2 Installation

Newer versions of Linux may not come with any sort of Python 2 installed. I recently wanted to run Sharpshooter, which is a “payload creation framework for the retrieval and execution of arbitrary CSharp source code.”

Problem is, Python 2 isn’t installed by default on Ubuntu 21.xx and neither is pip2. You also need to install an older (I think) version of jsmin – at least that’s what worked for me.

Use this script to install everything and get it up and running.

if [ "$EUID" -ne 0 ]
    then echo "Run as root!"
    exit
fi

# clone sharpshooter from github
git clone https://github.com/mdsecactivebreach/SharpShooter.git

add-apt-repository universe && apt update

apt install git curl

# install python2.7 and pip2
apt install python2.7 -y
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
chmod +x ./get-pip.py
sudo python2.7 ./get-pip.py

# install correct jsmin
wget https://files.pythonhosted.org/packages/17/73/615d1267a82ed26cd7c124108c3c61169d8e40c36d393883eaee3a561852/jsmin-2.2.2.tar.gz
tar xzf jsmin-2.2.2.tar.gz
python2.7 ./jsmin-2.2.2/setup.py install

Client Side Template Injection, huh?

I ran into this maybe two years ago and I didn’t even know it was a thing. I was actually trying to order some food (🍕) online and I noticed I could see the braces of a template being used.

I’m not sure there is a term for the braces being briefly exposed while a page is loading, but I’ve recently found out that VueJS has something to prevent this. I’m not too up to date with Vue, or Angular (in my case), so I’m a little out of my element.

Anyway, I saw the braces so I immediately tried some template injecting. It was odd to me because the page was IIS/aspx/dotnet, which usually isn’t a bastion of SSTI. And in my SSTI tests, Javascript functions like toUpperCase() were working, but other payloads that I thought may work did not work. At this point I was confused.

I eventually stumbled upon Client Side Template Injection. I kept messing around and finally was able to achieve XSS with the payload

{{$on.constructor('alert(1)')()}}

as seen here.

I notified the website management, and they did institute a fix. You can see the they only accepting valid alphanumeric characters at this point. Oddly, this fix works on some of their sites and on some of them it doesn’t. I don’t know what’s going on there.

See the following links for more information from people that know more than I do — Portswigger, HackTricks.

Do More with Tree (and why you should read the docs)

If you aren’t familiar with the Tree command in Linux, you should be. You can read about it here. Tree has been around for what seems like forever, and I’ve been using it for as long as I’ve been using Linux. With that said, I didn’t really know all that much about it until recently. The extent of my usage has always been something like this: $ tree -L 3 and that’s it.

Like most other Linux tools, there is much more to Tree than what I know. Take a look at the following command:

$ tree -LpDugC 2 -H .  > index.html 

This will create an index.html file that has a listing of everything in the dir in HTML form.

Anyway, you can install tree on Linux, Mac, and even Windows. There really wasn’t a huge point to this post — it’s just a reminder that your tools can do a lot more than what you’re probably already using them for. It pays to read the documentation.

Learning Go By Writing a POC for Gitlab CVE-2021-22205

I’ve been wanting to learn Go, and I learn by doing, so I decided to write a POC for CVE-2021-22205, which is fairly straightforward RCE in Gitlab that dropped a few weeks ago. My process in developing this went like this.

  1. Do thirty seconds of research to find a prior Golang POC for this CVE. I didn’t find one, but I’m sure they exist somewhere. I still would have written this, even if I found one. It would make for something to compare my poorly written code to.
  2. Start writing code. My thoughts the whole time while I was writing this were some variation of the following, “There must be a better way to do this.”
  3. Test.
  4. Rewrite.
  5. Repeat above for about 6 hours.
  6. Success!

I’m going to need more practice. I’ve been so used to python for the last ten years, moving to Golang is going to take some work.

Anyway, here is a link to my POC.

Tesla Solar, Powerwalls, Docker, Python, and Crypto Mining

I had Tesla solar panels and Powerwalls installed several weeks ago. I currently don’t have permission to operate (PTO) from my electricity provider, which means I can’t ship any of my surplus power back to the grid. So, after my batteries fill up for the day, I usually have power production that is going to waste. What can I do with that power?

Mine crypto, that’s what I can do! Those of you that know me IRL, know that I’ve been involved in crypto for a decade. Mining isn’t new to me, but I mostly gave up on it in 2012/2013 when I was only mining a few of Bitcoin a month and it wasn’t worth it to me anymore. Talk about a wrong decision…

I digress. I’m sitting here now producing extra power. Mining crypto with a graphics card that I already have will make me around $50-100/month and give me a chance to whip up a script in Python, which is what I truly enjoy in life. I haven’t done the actual math on it, but I think mining crypto is more profitable that selling my power back to my utility provider. It is also more fun to mine, lol.

My workstation that I’ll be mining on has a sole Gigabyte 1080 TI. It’s a little old, but they’re still going for $700 on eBay these days. I’m running Ubuntu 20.04, and I’ve decided to mine with a docker container and pointing my card at an ethash endpoint from NiceHash. I need to do some research to see if there are better options – which I assume exist.

My overall strategy for this operation will be pretty simple to start off. I’m just going to mine when my batteries are charged above a certain threshold. I set this threshold in the variable BATTERY_CHARGE_TO_START_MINING in the code. Yeah, I like long variable names.

Fortunately, Tesla provides an API to gather information from the Powerwall and there is a Python package to query it. To install this package use the following command:

pip3 install tesla_powerwall

And since I use this docker image to run the Trex Miner app, we also need to install the docker python package.

pip3 install docker

This script is pretty straightforward. I start a docker client to get the running images. I create a new Miner class with my wallet address and URL. This class has methods to start and stop the miner, as well as check if it is running.

Then, in a while loop I check my battery level and start and stop the miner as appropriate. I repeat this every HOW_OFTEN_TO_CHECK seconds.

Here is the code:

#!/usr/bin/env python3

import os
from tesla_powerwall import Powerwall
import docker
import time

POWERWALL_URL = ""  # PowerWall Gateway address goes here
EMAIL = ""  # email address that you use to login into the gateway
PASSWD = ""  # password that you use to log into the gateway
WALLET_ADDRESS = "35kwhvhyfnVnGdoWdyLqrtaHeY7RYByPfW"  # mining wallet address
MINING_URL = (
    "stratum+tcp://daggerhashimoto.usa-east.nicehash.com:3353"  # Mining url
)
# lowest battery charge where mining will start
BATTERY_CHARGE_TO_START_MINING = 50
# how often to check is battery level allows mining or not in seconds
HOW_OFTEN_TO_CHECK = 1800


def init():
    # initialize powerwall object and api
    powerwall = Powerwall(
        endpoint=POWERWALL_URL,
        timeout=10,
        http_session=None,
        verify_ssl=False,
        disable_insecure_warning=True,
        pin_version=None,
    )
    powerwall.login(PASSWD, EMAIL)

    api = powerwall.get_api()

    return powerwall, api


class Miner:
    def __init__(self, client, wallet_address, mining_url):

        self.wallet_address = wallet_address
        self.mining_url = mining_url
        self.client = client
        return

    def start_miner(self, client):
        env_vars = {
            "WALLET": WALLET_ADDRESS,
            "SERVER": MINING_URL,
            "WORKER": "Rig",
            "ALGO": "ethash",
        }
        try:
            client.containers.run(
                "ptrfrll/nv-docker-trex:cuda11",
                detach=True,
                runtime="nvidia",
                name="trex-miner",
                ports={4067: 4067},
                environment=env_vars,
            )
        except os.error as e:
            client.containers.get("trex-miner").restart()
        return

    def stop_miner(self, client):
        trex = client.containers.get("trex-miner")
        trex.stop()
        return

    def is_running(self):
        try:
            client.containers.get("trex-miner")
            return True
        except os.error:
            return False


if __name__ == "__main__":
    powerwall, api = init()

    client = docker.from_env()

    miner = Miner(client, WALLET_ADDRESS, MINING_URL)

    miner.start_miner(client)

    while True:
        # powerwall charge is satisfactory, start mining
        if not miner.is_running() and (
            api.get_system_status_soe()["percentage"]
            > BATTERY_CHARGE_TO_START_MINING
        ):
            miner.start_miner(client)
            print("miner is running or will be started")
        # powerwall charge is too low, shut off mining
        elif miner.is_running() and (
            api.get_system_status_soe()["percentage"]
            < BATTERY_CHARGE_TO_START_MINING
        ):
            print("stopping miner")
            miner.stop_miner(client)
        # try again
        time.sleep(HOW_OFTEN_TO_CHECK)

You can also find future updates of the code here.

TODO: add more options to start/stop mining e.g. if my panels/batteries are connected to the grid or not, start/stop mining based on the weather, etc.

TODO: rewrite in Golang. Trying to learn Go.