lundi 31 mars 2014

Scapy - Rmcp / ipmi fuzzer

J'avais déjà joué avec scapy  dans un billet précédent et je me suis demandé à quel point c'est difficile de rajouter un nouveau protocole... Suite à ça j'ai cherché un truc raisonnablement simple et sympa et je me suis penché sur Rmcp / ipmi ...
Ipmi d'après Wikipedia est: "L'Interface de gestion intelligente de matériel, (ou IPMIIntelligent Platform Management Interface) est un ensemble de spécifications d'interfaces communes avec du matériel informatique (principalement des serveurs) permettant de surveiller certains composants (ventilateur, sonde de température, ...), mais également de contrôler l'ordinateur à distance, reboot, interrupteur, console à distance."
En gros ça se présente sous la forme d'un chip intégré à la carte réseau d'un serveur et ça écoute pour recevoir des commande bas niveau de type combien de courant consomme tu ? coupe l'alimentation, effectue un redémarrage par acpi et ainsi de suite ... Les firmwares embarqués sont toujours de bon candidat au fuzzing car pas souvent mis à jour et mis à jour pénible et c'est pas comme si ça n'avait pas posé des problèmes dans le passé ...


#! /usr/bin/env python
#vim: set fileencoding=latin-1
# Author: Jérémie Banier
# Date: Oct. 29 2013
# Purpose: implement / test ipmi protocol with scapy
# Based on test add-ons sample 
# usage:

import logging
# Set log level to benefit from Scapy warnings
logging.getLogger("scapy").setLevel(1)

from scapy.all import *

class Rmcp(Packet):
    name = "Remote Management Control Protocol"
    fields_desc=[ LEShortField("Version",0x06),
        ByteField("Sequence", 0xFF) ,
        XByteField("Type and Class", 0x07) , 
        ByteEnumField("Authentication type", 0, {0:'None', 6:'RMCP+'}), ]

bind_layers( UDP, Rmcp, sport=623 )
bind_layers( UDP, Rmcp, dport=623 )

class IPMISessionLayer(Packet):
    name = "IPMI Session Wrapper"
    fields_desc=[ IntField("Session sequence number", 0),
            XIntField("Session ID", 0),
            ByteField("Message length", 0), ]

class IPMISessionLayer2(Packet):
    name = "IPMI Session Wrapper v2.0+"
    fields_desc=[ ByteEnumField("Payload type", 0x10, 
                {0x10:"Open session request", 0x11:"Open session response",
                    0x12:"RAKP Message 1", 0x13:"RAKP Message 2"
                    }),
            IntField("Session sequence number", 0),
            XIntField("Session ID", 0),
            ByteField("Message length", 0), ]

bind_layers( Rmcp, IPMISessionLayer, {'Authentication type':0} )
bind_layers( Rmcp, IPMISessionLayer2, {'Authentication type':6} )

class IPMILayer(Packet):
    name = "Intelligent Platform Management Interface"
    fields_desc = [ ByteField("Target address", 0x20), ByteEnumField("Target LUN", 0x18, {0x18:"NetFN Application Request"}),
            ByteField("Header checksum", 0xc8), ByteField("Source address", 0x81),
            ByteField("Source LUN", 0x00), 
            ByteEnumField("Command", 0x38, {0x38:"Get Channel Auth. cap."}),
            ByteEnumField("Version compat.", 0x0e, {0x0e:"IPMI v2.0+"}),
            ByteEnumField("Requested privilege level", 0x04, {0x04:"Administrator"}),
            ByteField("Data checksum", 0xb5) ]

bind_layers( IPMISessionLayer, IPMILayer )

if __name__ == "__main__":
    interact(mydict=globals(), mybanner="IPMI fuzzer")

Le script en est encore à l'état d'ébauche mais on peut déjà l'utiliser pour tester l'API de Scapy:
└───> ./Rmcp.py
WARNING: No route found for IPv6 destination :: (no default route?)
Welcome to Scapy (2.2.0)
IPMI fuzzer
>>> t= rdpcap('ipmi2.pcap')
>>> t[16].show()
###[ cooked linux ]###
  pkttype= sent-by-us
  lladdrtype= 0x1
  lladdrlen= 6
  src= '\x00\x1d\tlg,'
  proto= IPv4
###[ IP ]###
     version= 4L
     ihl= 5L
     tos= 0x0
     len= 76
     id= 9063
     flags= DF
     frag= 0L
     ttl= 64
     proto= udp
     chksum= 0x5c0a
     src= 10.200.82.208
     dst= 10.201.82.207
     \options\
###[ UDP ]###
        sport= 41227
        dport= asf_rmcp
        len= 56
        chksum= 0xbb79
###[ Remote Management Control Protocol ]###
           Version= 6
           Sequence= 255
           Type and Class= 0x7
           Authentication type= RMCP+
###[ IPMI Session Wrapper v2.0+ ]###
              Payload type= Open session request
              Session sequence number= 0
              Session ID= 0x0
              Message length= 32

Il me reste encore à implémenter l'ouverture de session, ce qui permettrais de faire du brute force sur le mot de passe admin par exemple (encore qu'il faudrait pour cela que le chip ipmi soit accessible depuis internet ce qui serait pas un très bonne idée pour dire ça poliment)
Une affaire à suivre avec heureusement des détails croustillants :P et une fin heureuse ...

mardi 11 mars 2014

Using bcfg2 data to generate IDS config file.

We use Suricata as and IDS and our typical configuration file define list of hosts sharing the same firewall rules, the firewall rules are function of the purpose of the box and since our configuration management tool (BCFG2) knows all about that, I was curious to see if I could generate part of the configuration using bcfg2 ...
#!/usr/bin/python

""" the idea is to use the bcfg2 backend to generate a list of ip's and associated with heir security groups
 like bcfg2-info groups give the list of groups and bcfg2-client query -g "group" gives the list of bcfg2-client
then resolve it like SER = "1.1.1.1,1.1.1.2,...." and so on
this should allow us to genereate normal rules for every security group and stuff that into the ids to spot any non compliance with the
firewall ruleset, possibly some other shit as well ? """

import os
import re
import socket
import sys
import cmd
import getopt
import fnmatch
import logging
import lxml.etree
import traceback
from code import InteractiveConsole
import Bcfg2.Logger
import Bcfg2.Options
import Bcfg2.Server.Core
import Bcfg2.Server.Plugin
import Bcfg2.Client.Tools.POSIX


def main():
    optinfo = dict(profile=Bcfg2.Options.CORE_PROFILE,
        command_timeout=Bcfg2.Options.CLIENT_COMMAND_TIMEOUT)

    optinfo.update(Bcfg2.Options.INFO_COMMON_OPTIONS)
    setup = Bcfg2.Options.OptionParser(optinfo)
    setup.hm = "\n".join(["prepareIdsMetadata","Options:",setup.buildHelpMessage()])
    setup.parse(sys.argv[1:])

    Bcfg2.Logger.setup_logging('prepareIdsMetadata', to_syslog=False, level=0)

    bcfg2 = Bcfg2.Server.Core.BaseCore(setup)
    bcfg2.load_plugins()
    bcfg2.block_for_fam_events(handle_events=True)
    f = open("group.txt", "w")
    for group in list(bcfg2.metadata.groups.keys()):
        hosts =  bcfg2.metadata.get_client_names_by_groups([group])
        first = True
        if hosts :
            f.write(group.upper() + '="')
            for h in hosts:
                try:
                    (hostname, aliases, ips) = socket.gethostbyname_ex(h)
                    if first:
                        f.write(ips[0])
                        first = False
                    else:
                        f.write(',' + ips[0])
                except:
                    pass
            f.write('"\n')
    f.close()

if __name__ == '__main__':
    sys.exit(main())

Which will generate a text file (group.txt) containing something like that:

DEBIAN-SQUEEZE="10.5.1.21,10.5.1.22,10.5.1.23,10.5.1.24"
SIP="10.21.81.249"
MYSQLNDBAPI="10.4.16.101,10.4.16.102,10.4.16.103,10.4.16.104"

Which I can then add as groups in my suricata yaml config file, and then build rules specific to those host purpose :-) Some host appears in several groups since some of our groups are OS based (CentOS/Debian-Wheezy ... ), geographic (BE, US, ...) and purpose based (WEB, PROXY, DB, ...)

The obvious advantages are to keep everything consistent, get rid of duplicate information and automatic updates of the IDS whenever you add an hosts. bcfg2 also has a plugin for Nagios which is nice to use too to make sure no host is left behind.

Thanks to the people on #bcfg2 on freenode for their support.

Hadoop / Spark2 snippet that took way too long to figure out

This is a collection of links and snippet that took me way too long to figure out; I've copied them here with a bit of documentation in...