Microsoft 365 Security: Using the API for Live Response

Adding a file to the MDE Live Response Library and Running it on an Endpoint

Continuing to build on my previous posts…

One of my all time favorite user containment methods is to change the BitLocker keys and shut the system down.

#Delete existing protections
manage-bde.exe -protectors -delete c: -Type TPMAndPIN
manage-bde.exe -protectors -delete c: -Type RecoveryPassword
#Now that all of the previous Protectors are gone, let's add our own.
#Let's add a new password that only the security team will need to know.
manage-bde.exe -protectors -add c: -TPMandPIN SuperDuperPassword
#As a backup, let's also add a couple of recovery keys
manage-bde.exe -protectors -add c: -RecoveryPassword 111111-111111-111111-111111-111111-111111-111111-111111 
manage-bde.exe -protectors -add c: -RecoveryPassword 222222-222222-222222-222222-222222-222222-222222-222222 
manage-bde.exe -on c:
shutdown /f /s /t 0

Making a function to Post Library Files


def postFileToLiveResponseLibrary(fileObjToUpload, description, parameters="", override = False, token=result["access_token"]):
    headers = {'Authorization' : "Bearer " + token }
    url = "https://api.securitycenter.windows.com/api/libraryfiles"
    files={'file': fileObjToUpload}

    params={
          'Description': description,
          'ParametersDescription': parameters,
          'OverrideIfExists':override
    }
    response = requests.post(url, data=params,  files=files, headers=headers, verify=False)
    return response.json()
    

Adding a Containment Script to Your Live Response Library

from io import StringIO 
script = """
        #Delete existing protections
        manage-bde.exe -protectors -delete c: -Type TPMAndPIN
        manage-bde.exe -protectors -delete c: -Type RecoveryPassword
        #Now that all of the previous Protectors are gone, let's add our own.
        #Let's add a new password that only the security team will need to know.
        manage-bde.exe -protectors -add c: -TPMandPIN SuperDuperPassword
        #As a backup, let's also add a couple of recovery keys
        manage-bde.exe -protectors -add c: -RecoveryPassword 111111-111111-111111-111111-111111-111111-111111-111111 
        manage-bde.exe -protectors -add c: -RecoveryPassword 222222-222222-222222-222222-222222-222222-222222-222222 
        manage-bde.exe -on c:
        """

#StringIO is a fun module that lets you treat strings like file objects. 
fileobj = StringIO(script)
fileobj.filename = "contain.ps1"

postFileToLiveResponseLibrary(
    fileObjToUpload = fileobj,
    description = "BitLocker Containment Script",
    parameters = "Parameter Info Would Go Here",
    override = True
    )
{'@odata.context': 'https://api.securitycenter.windows.com/api/$metadata#LibraryFiles/$entity',
 'fileName': 'file',
 'sha256': '8052835f7fcbca7b123e763b3e429f552cda4d6d3ddcf193d4b356ca01b9e7b4',
 'description': 'BitLocker Containment Script',
 'creationTime': '2022-03-10T13:35:27.841749Z',
 'lastUpdatedTime': '2022-03-10T13:35:27.841749Z',
 'createdBy': 'Jonathan Glass',
 'hasParameters': False,
 'parametersDescription': 'Parameter Info Would Go Here'}

Sweet! It worked!

Running your newly uploaded script against an endpoint

liveResponseCommands = {}
liveResponseCommands['Commands'] = []

command = {}
command['type'] = "RunScript"
command["params"] = [{"key":"ScriptName",
                      "value":"contain.ps1"}]

liveResponseCommands['Commands'].append(command)
liveResponseCommands['Comment'] = "Containing Endpoint via BitLocker"

liveResponseCommands

{'Commands': [{'type': 'RunScript',
   'params': [{'key': 'ScriptName', 'value': 'contain.ps1'}]}],
 'Comment': 'Containing Endpoint via BitLocker'}
machineID = "c6993a6c004946bf52d2d59237c18e6db4db7e5e"
MDErequest("machines/%s/runliveresponse" % (machineID),
           liveResponseCommands)
{'@odata.context': 'https://api.securitycenter.windows.com/api/$metadata#MachineActions/$entity',
 'id': '9509426d-490c-4f2b-90b4-4703674be8cf',
 'type': 'LiveResponse',
 'title': None,
 'requestor': 'JonathanGlass@HalfFullofSecurity.onmicrosoft.com',
 'requestorComment': 'Containing Endpoint via BitLocker',
 'status': 'Pending',
 'machineId': 'c6993a62004946bf52d2d59237c18e6db4db7e5e',
 'computerDnsName': 'desktop-l3mnbj9',
 'creationDateTimeUtc': '2022-03-10T14:36:51.8759111Z',
 'lastUpdateDateTimeUtc': '2022-03-10T14:36:51.8759111Z',
 'cancellationRequestor': None,
 'cancellationComment': None,
 'cancellationDateTimeUtc': None,
 'errorHResult': 0,
 'scope': None,
 'externalId': None,
 'requestSource': 'PublicApi',
 'relatedFileInfo': None,
 'commands': [{'index': 0,
   'startTime': None,
   'endTime': None,
   'commandStatus': 'Created',
   'errors': [],
   'command': {'type': 'RunScript',
    'params': [{'key': 'ScriptName', 'value': 'contain.ps1'}]}}],
 'troubleshootInfo': None}

Deleting a file from your Live Response Library

def deleteFileFromLiveResponseLibrary(filename, token=result["access_token"]):
    headers = {'Authorization' : "Bearer " + token }
    url = "https://api.securitycenter.windows.com/api/libraryfiles/%s" % (filename)
    response = requests.delete(url, headers=headers, verify=False)
    return response

deleteFileFromLiveResponseLibrary("contain.ps1")

<Response [204]> means it worked!