
Code Review: xz/liblzma Backdoor
Diving into commits made by the xz/liblzma actors to look for interesting behaviors and artifacts.
Bots can be characterized as a body of code that implements triggers to execute tasks.
Triggers can materialize as very different things depending on the application. They could by physical levers, buttons, voice commands, text commands or a set of criteria based on arbitrary metrics.
Tasks may be a specific action or set of actions that are repeatable and with known variables. These could be something like engaging a servo, turning on a light, creating a log entry, communicating with internet services, or running commands on remote computers.
For this project we are going to focus on providing a simple service: a project name generator.
Quick Note: Automation can be a slippery scope, which will be covered separately elsewhere(TBD). For now it’s safe to summarize that when planning for how to implement, use, and maintain automation resources it is necessary to evaluate the cost to benefit ratio ahead of time. Often times automation takes at least a few times longer to automate any given processs than the process itself takes to complete. So it’s probably not worthwhile to automate something you’re only going to do seldomly. Focus on highly repeatable or error prone tasks where you can reduce the risk of human error.
When spinning up a new code project or conceptualizing a design, it can be time consuming or frustrating coming up with a name to refer to it by. You want something unique, telling, and fitting for your project. Alternatively, you can generate a codename or a “temporary” name as a placeholder until you have a better idea of the concept. This service will be a simple endpoint to assist in that process, implemented as an application within Slack.
There are a lot of ways to generate these types of dictionaries. Very simple versions could just use simple adjectives or colors as a prefix to an animal or simple object. Something like blue bicycle or grey sky would be sufficient. For the purpose of this exercise we’re going to use one that exists.
Since we care about having the type of word we’re selecting from, we need to find a pregenerated wordlist organized by type of word. As it so happens, Princeton already publishes one here.
# Download the tarball
wget -O wordlist.tar.gz <download-tar-gzip-link>
# Extract the tarball
tar -xzf wordlist.tar.gz
Alternatively, you can download and extract the zip into the working directory.
After downloading and extracting the files we will have a dictionary for each of verbs, adverbs, adjectives, and nouns. For the purpose of this project I will only be focused on adverbs, adjectives, and nouns.
When evaluating a language, it is important to take into account the usefulness of a given language based on the context of needs. For this project we need to be able to deal with REST APIs (HTTP), reading files, and randoms.
Many languages work with these things, but Python is relatively easy to read and implement so we will start there.
When using python, we want to install packages just for this application, not globally. For this purpose we’ll use a feature called VEnv (Virtual Environment), to localize everything into our project folder. This also keeps you from running into package version conflicts when you’re creating many disparate python projects on the same system.
On my Debian-based Ubuntu system, the setup looks like this:
# global install Python3 and Python3-VEnv
sudo apt install python3 python3-venv
# create VEnv project directory
python3 -m venv CodenameBot
# move into directory, and activate the VEnv
cd CodenameBot
. bin/activate
If you’re using any other linux flavor, apt
may be substituted for yum
, pacman
, etc.
Move the extracted dictionary files into the new python project folder so we can load them into Python, and we will begin with some code.
# We will assume that we're leaving off from the prior section
# where we have already done '. bin/activate'
# Move the files into the project directory
mv ../dict.* ./
We should now have dict.adv
, dict.adj
, and dict.noun
in the project directory.
With the IDE of your choice, let’s create a new python file: main.py
# The binary name for VScode on Windows and Linux is just 'code'
code main.py
There are 3 basic steps we need to do for generating random names from these lists:
Let’s set our main variables and import any packages we need.
# Import the random.choice function:
# see https://docs.python.org/3/library/random.html#random.choice
from random import choice
# Prepare the variables we want to use for our wordlists
adverbs = []
nouns = []
adjectives = []
Now let’s load the files into our variables.
# Open each wordlist, and load *valid* words into your variables
with open('dict.adv', 'r') as advFile:
for line in advFile:
word = line.strip() # remove extra spaces/newlines
if len(word) > 3:
adverbs.append(word)
with open('dict.noun', 'r') as nounFile:
for line in nounFile:
word = line.strip() # remove extra spaces/newlines
if len(word) > 3:
nouns.append(word)
with open('dict.adj', 'r') as adjFile:
for line in adjFile:
word = line.strip() # remove extra spaces/newlines
if len(word) > 3:
adjectives.append(word)
With the wordlists loaded up, let’s pick a random word from each and format it.
# assign variables with randomly chosen words
adverb = choice(adverbs)
noun = choice(nouns)
adjective = choice(adjectives)
# we will use a format-string to print the variables to output
print(f"{adverb} {adjective} {noun}")
Great! Now let’s save the file, and give it a run.
python3 main.py
# output:
# indecently active choreographer
Slack has two avenues of communication for user-initiated events:
The easiest to implement solution is to have Slack send the event to your Bot, as it doesn’t require a great understanding of the Slack API to implement, and it doesn’t require any specialized formatting for extremely basic responses such as those for this project. For this reason, this is the method we will use for this exercise.
In order to field HTTP requests from Slack or any HTTP client on the internet, we need to implement an HTTP server. There are a plethora of options available including a vanilla HTTP server built into python, but the most well documented and easy to setup is probably Flask. So for the purpose of ease of use and relatively secure default settings, we will use Flask.
To begin, let’s add Flask to our python environment.
# we will again load our environment in bash
# skip this step if you're active following along from earlier
cd project-folder
. bin/activate
# now let's add the required packages
pip install Flask
# reopen the main.py file, if it's not already open, to setup the HTTP server
code main.py
Since in our original code the random selection happens in the ‘root’ context (outside of a function), it only gets called once when we run the script. What we want to happen, is that everytime Slack hits our API, it should generate a new project name. The easiest method to ensure this happens is to move the random name creation into a function, as shown below.
"""
ORIGINAL CODE
"""
# assign variables with randomly chosen words
adverb = choice(adverbs)
noun = choice(nouns)
adjective = choice(adjectives)
# we will use a format-string to print the variables to output
print(f"{adverb} {adjective} {noun}")
"""
NEW CODE
"""
def getProjectName():
# assign variables with randomly chosen words
adverb = choice(adverbs)
noun = choice(nouns)
adjective = choice(adjectives)
# we will use a format-string to print the variables to output
return f"{adverb} {adjective} {noun}"
# generate 3 project names to test
print(getProjectName())
print(getProjectName())
print(getProjectName())
Now, let’s see what happens when we run it.
python3 main.py
# output:
# Wildly Bladdery Rainmaker
# Elsewhere Esteemed Mind
# Unselfishly Aweary Stripe
Great!
Before we move on, go ahead and delete the 3 ‘print’ lines we added there for testing.
Now we need to write the HTTP server part, where Slack talks to our app. To do that, we need to create an instance of Flask
so we can accept HTTP requests.
# [... previous code above ]
from flask import Flask
# call a new instance of Flask into our code as 'app'
app = Flask(__name__)
Now that we have an instance of app in our script, we can assign it routes.
Routes in HTTP server land are what we as users refer to as “URLs” or “paths”. It is the file/folder suffix that goes behind the domain name of a website. So when you see “someserver.online.com/about
”, the route for this is “/about
”.
There are more complicated implementations of routes where they can be nested in a hierarchy, but for this exercise we’ll focus on single-tiered routes.
Knowing that we need a route for our application, and since currently it only has one purpose, I am going to use the root path for our project. This is commonly the default route for a website, and is referred to simply in routing as “/
”.
So create your route as shown below, or set a custom name for your application following the forward-slash such as “/project
” or “/codename
”.
# [... previous code above ]
# This notation below with a leading '@' sign is called a decorator.
# These are a more complex concept in python, so I won't elaborate too much.
# The basic concept of a decorator is that it is a function that wraps other functions.
# It allows for the seamless modification of inputs and outputs of the wrapped function.
# see: https://realpython.com/primer-on-python-decorators/
# Create a new HTTP endpoint, or 'path' for slack to talk to
# For the purpose of this project, we'll use the root path '/'
@app.route('/', methods=['GET', 'POST'])
def newProject():
return getProjectName()
Now when someone makes an HTTP request for this route, it will simply return a random project name as text.
In order to start the HTTP server we need to do one more thing, which is telling it to run the server. Right now, all of our code with the exception of our two functions lives in the root context of our application.
We’re going to create a conditional code section that starts “app.run()
” when we’re running the python file directly.
# [... previous code above ]
if __name__ == "__main__":
app.run(port=5000)
Great. Save your work, and let’s give it a run.
python3 main.py
In your browser, you chould be able to navigate to the webpage on port 5000 and get a random project name everytime you refresh, or in a separate terminal you can curl the port on your system.
# assuming you're running curl on the same linux server
# and if your route from above isn't the root '/', then use your route suffix
curl "http://127.0.0.1:5000/"
That’s it!
We now have a working python HTTP server that responds to requests with a random project name.
Alright, so now that we have a working HTTP Server we need to make it accessible by Slack. This part will be a bit tricky, because we don’t want to just throw everything on the internet.
One of the quickest and easiest solutions is to use ngrok. Ngrok is an HTTP proxy service that enables you to take an internal service (running on 127.0.0.1:5000
for instance), and publish it on the internet behind a proxy service.
One of the downsides to ngrok is that it does not give you a permanent endpoint, so this solution is purely for educational purposes.
When you go to running something similar for more permanent (but non-production) use cases you will probably want to look at a paid account for ngrok to get a static name, or putting it on a proper cloud compute host such as AWS Lightsail or alternative cheap VPS provider and fronting it with an HTTP proxy like Apache or Nginx which we will cover elsewhere(TBD).
For now, ngrok
on Debian-based x64 systems can be run like below, assuming you’re running your app on localhost:5000
wget <ngrok-download-link>.zip
unzip ngrok-stable-linux-amd64.zip -d ngrok
cd ngrok
./ngrok http 5000
# output
# Session Status online
# Session Expires 1 hour, 59 minutes
# Version 2.3.40
# Region United States (us)
# Web Interface http://127.0.0.1:4040
# Forwarding http://random-prefix.ngrok.io -> http://localhost:5000
# Forwarding https://random-prefix.ngrok.io -> http://localhost:5000
Copy the “Forwarding
” address information, as we’ll need this for Slack to talk to your app.
Creating a Slack app is pretty straight forward. The only requirement is to create a Slack account, and then create or join a workspace.
In order to add your app to a team you will need to be an admin or you can ask for permission to have it added.
Now we have our own app! Let’s go ahead and tell Slack how our app will operate.
On the landing page for app management, is a section called “Add features and functionality”.
For this exercise, I will only be using “Slash Commands”
ngrok
forwarding address from aboveFollowing setup of the user interface, click “Basic Information” in your App navigation.
From the “Basic Information” page for your App, go ahead and install it to your workspace
That’s it! You can now use the app from within your workspace.
We’re in the homestretch now.
You just have to make sure the app and ngrok are ready to go.
cd project-directory
. bin/activate
python3 main.py
# output:
# application is running on 127.0.0.1:5000
cd ngrok
./ngrok http 127.0.0.1:5000
# output:
# Session status: Online
# [ ... ]
If you restarted ngrok
, you’re going to need to go update the Request URL in your Slack App.
Forwarding
” address from ngrok
Now, open Slack and test out your command!
What you should receive back is an ephemeral message (sent only to you) with a random project name.
# Import the random.choice function:
# see https://docs.python.org/3/library/random.html#random.choice
from random import choice
from flask import Flask
# Prepare the variables we want to use for our wordlists
adverbs = []
nouns = []
adjectives = []
# Open each wordlist, and load *valid* words into your variables
with open('dict.adv', 'r') as advFile:
for line in advFile:
word = line.strip() # remove extra spaces/newlines
if len(word) > 3:
adverbs.append(word)
with open('dict.noun', 'r') as nounFile:
for line in nounFile:
word = line.strip() # remove extra spaces/newlines
if len(word) > 3:
nouns.append(word)
with open('dict.adj', 'r') as adjFile:
for line in adjFile:
word = line.strip() # remove extra spaces/newlines
if len(word) > 3:
adjectives.append(word)
def getProjectName():
# assign variables with randomly chosen words
adverb = choice(adverbs)
noun = choice(nouns)
adjective = choice(adjectives)
# we will use a format-string to print the variables to output
return f"{adverb} {adjective} {noun}"
# call a new instance of Flask into our code as 'app'
app = Flask(__name__)
# This notation below with a leading '@' sign is called a decorator.
# These are a more complex concept in python, so I won't elaborate too much.
# The basic concept of a decorator is that it is a function that wraps other functions.
# It allows for the seamless modification of inputs and outputs of the wrapped function.
# see: https://realpython.com/primer-on-python-decorators/
# Create a new HTTP endpoint, or 'path' for slack to talk to
# For the purpose of this project, we'll use the root path '/'
@app.route('/', methods=['GET', 'POST'])
def newProject():
return getProjectName()
if __name__ == "__main__":
app.run(port=5000)
This exercise is largely for exploring the concept of writing Slack-initiated automation. It is important to call out that the way things are configured for this exercise would not be ideal to deploy a production application. These are some aspects you may want to explore if you are interested in taking a concept like this to production use:
To improve the availability of a simple python application, you should look at a few added features:
Based on the concepts in this article, there are a number of topics to read up on for core understanding expansion
Starting with something simple can help give you a jumpstart on other concepts. Maybe you want a Slack bot that looks up CVEs, or dorks Google and returns high-confidence responses, or something that returns ExploitDB docs based on keyword search and a date range.
Diving into commits made by the xz/liblzma actors to look for interesting behaviors and artifacts.
An article on technology basics, from the devices we use to how they connect.