System Center Configuration Manager (SCCM) 2012: Client PKI and Subordinate CA woes

When trying to setup SCCM on my network, I came upon some trouble getting the secure communication working between the server and the client (PKI settings and HTTPS communication). I finally ended up figuring out the issue after a few good hours of debugging and log hunting, and so hopefully this information might help someone else out.

I had set up SCCM on one of my servers, configured it to use PKI communication with the clients. I had a root CA and also a subordinate CA (the subordinate was issuing certificates to the computers). All that being set up, I decided to deploy the client using the automatic deployment method through SCCM. Looked at the client and realized that it was failing to communicate with the server correctly. Alright then, so lets go ahead and check the logs in C:\Windows\CCM\Logs :

In CcmMessaging.log:

<![LOG[Begin searching client certificates based on Certificate Issuers]LOG]!><time="18:39:06.247+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="3868" file="ccmcert.cpp:3759">
<![LOG[Certificate Issuer 1 [E=austrianalex@gmail.com; CN=AustrianAlex CA; OU=Certificate Authority; O=AustrianAlex; L=Spokane; S=Washington; C=US]]LOG]!><time="18:39:06.247+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="3868" file="ccmcert.cpp:3775">
<![LOG[Finding certificate by issuer chain returned error 80092004]LOG]!><time="18:39:06.247+420" date="10-11-2012" component="CcmMessaging" context="" type="2" thread="3868" file="ccmcert.cpp:3884">
<![LOG[Completed searching client certificates based on Certificate Issuers]LOG]!><time="18:39:06.247+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="3868" file="ccmcert.cpp:3918">
<![LOG[Unable to find any Certificate based on Certificate Issuers]LOG]!><time="18:39:06.247+420" date="10-11-2012" component="CcmMessaging" context="" type="2" thread="3868" file="ccmcert.cpp:3995">
<![LOG[Raising event:

instance of CCM_ServiceHost_CertRetrieval_Status
{
DateTime = "20121012013906.253000+000";
HRESULT = "0x87d00215";
ProcessID = 684;
ThreadID = 3868;
};
]LOG]!><time="18:39:06.253+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="3868" file="event.cpp:729">
<![LOG[Post to https://MYCCMSERVER/ccm_system/request failed with 0x87d00231.]LOG]!><time="18:39:06.255+420" date="10-11-2012" component="CcmMessaging" context="" type="2" thread="3868" file="messagequeueproc_outgoing.cpp:430">

Finding certificate by issuer chain returned error 80092004. I had specified my root CA in the SCCM console, and I thought that was enough. But certificate chain? My subordinate CA is issuing these certs, but if it trusts the root, it should also trust the subordinate, right? I decided to go and add the subordinate CA certificate into the site settings as well.

Client Settings for SCCM

So now I had the Root CA and subordinate CA specified in the settings. Pushed out the client again. This time I got a different error.

In CcmMessaging.log:

<![LOG[Begin searching client certificates based on Certificate Issuers]LOG]!><time="19:09:24.100+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="5832" file="ccmcert.cpp:3759">
<![LOG[Certificate Issuer 1 [E=austrianalex@gmail.com; CN=AustrianAlex CA; OU=Certificate Authority; O=AustrianAlex; L=Spokane; S=Washington; C=US]]LOG]!><time="19:09:24.100+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="5832" file="ccmcert.cpp:3775">
<![LOG[Certificate Issuer 2 [CN=AustrianAlex Windows CA]]LOG]!><time="19:09:24.100+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="5832" file="ccmcert.cpp:3775">
<![LOG[Skipping Certificate [Thumbprint 9F5CAC8D5572421BA2EEAB2BDC2AAFB8A41365FC] issued to 'TheClient' as root is 'AustrianAlex CA']LOG]!><time="19:09:24.101+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="5832" file="ccmcert.cpp:3877">
<![LOG[Completed searching client certificates based on Certificate Issuers]LOG]!><time="19:09:24.101+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="5832" file="ccmcert.cpp:3918">
<![LOG[Unable to find any Certificate based on Certificate Issuers]LOG]!><time="19:09:24.101+420" date="10-11-2012" component="CcmMessaging" context="" type="2" thread="5832" file="ccmcert.cpp:3995">
<![LOG[Raising event:

instance of CCM_ServiceHost_CertRetrieval_Status
{
DateTime = "20121012020924.107000+000";
HRESULT = "0x87d00215";
ProcessID = 1224;
ThreadID = 5832;
};
]LOG]!><time="19:09:24.107+420" date="10-11-2012" component="CcmMessaging" context="" type="1" thread="5832" file="event.cpp:729">
<![LOG[Post to https://MYCCMSERVER/ccm_system/request failed with 0x87d00231.]LOG]!><time="19:09:24.109+420" date="10-11-2012" component="CcmMessaging" context="" type="2" thread="5832" file="messagequeueproc_outgoing.cpp:430">

Skipping Certificate [Thumbprint 9F5CAC8D5572421BA2EEAB2BDC2AAFB8A41365FC] issued to ‘TheClient’ as root is ‘AustrianAlex CA’. Apparently, the chain issue went away and now it is ignoring the certificate entirely. Double checked and made sure that the root CA matched the issued certificate and it did. This is when I realized that since the CA certificates were already installed as Trusted Root Certificates (through Group Policy in my case) on the client machine, there might be a conflict with those I was specifying in the SCCM site settings. They were both the same Root CA, however, the SCCM client’s logic decided to…skip any certificates that matched the CA…even though they were the same? I still don’t get the behavior, but whatever, the solution turned out to be to remove the specified CA’s from the SCCM site settings:
SCCM Site Settings Client Connection

Also, I had to make sure to uninstall the client from the computer before doing an automatic push install to purge the settings (otherwise, doing another automatic push install will continue to use the specified certificates, getting the same message as above). I did this by executing the following on the client computer:

cd c:\Windows\ccmsetup
ccmsetup.exe /uninstall

ccmsetup.exe /uninstall

After that, a quick push install and the client started communicating using PKI.
Client install SCCM 2012

TL;DR:

If you have the root CA already installed on a client computer, do not specify a root CA in the SCCM console site settings, or you’re going to have a bad time.

Posted in Microsoft | 2 Comments »

Effective Social Engineering: Another DEFCON 20 skytalk

The way a lot of security pen-testers do social engineering calls by phone – “Hey, I’m <so and so> from IT. You have a virus. I need your password.” Rinse, repeat, and occasionally, you will get one or two people who will actually give you their password. The other 99% of the time, employees have been trained not to give out their password and rightfully so. “Well, we got a username and password, so it still counts…” But you also wasted time calling 50 other people before you got the credentials, which most likely put the company on high alert.

At the DEFCON 20 Skytalk, the presenter with the alias “Tanks4U” talked about the inefficiencies and the problems with using the commonly employed methods of social engineering, and the companies who hire security guys to call <x> amount of people for statistical purposes – specifically if it’s an all or nothing goal like getting credentials. You didn’t get them to give you the password? Hang up. They challenged or questioned you? Hang up. Try again with some other person. And it totally compromises the whole point to social engineering.

“You’re not supposed to tell them to jump in a river. You are supposed to take them to the river and convince them that they are on fire.” Social engineering isn’t just about a cold call to a person, it involves an interaction with the person on the other side and observation of person to get the reaction you need. In effective social engineering, you need to know the following:

  • Know your goals – what details do you want to obtain? Username and password could be one, but what if you want the building layout? When does your cleaning staff come in? Who are the managers? Any details on the business that you can obtain can help you further in obtaining access to their business. Write them out on a piece of paper in a checklist. Make a scoring system. Ask them for things like the last four digits of their phone number, how their badges look like, last four digits of their SSN, etc.
  • Know your target – it helps to observe how people react when faced with certain situations for this one. Go to a coffee shop before hand and sit there for a few hours. Watch how people interact with one another. You don’t have to be good at knowing how to social engineer people, just good enough to figure out how to small talk with others. Ask them questions about the weather or talk about hardships (ever had a flat tire or kids?), or engage in other commonalities that people share. Make a connection.
  • Know yourself – sure you might know how to talk to others, but can you actually do it when it comes time? How will you react? Most people might get nervous and that is simply something you get over after you have some practice with social engineering. Public speaking also helps (try performing in a play and get your acting skills in shape). Or just sit in front of a mirror and practice your technique. Ultimately, though, it will come down to analyzing how you react in a live situation with other people. Remember the coffee shop? Go make small talk with those people that you were just observing.

In terms of the actual call or contact, performing the following steps will be key to establishing a good baseline for social engineering (or not! depending on the situation. Adjust accordingly with what you know above):

  • Initiation – “Hi, my name is <so and so>. Is this <target>?”
  • Pleasantries – “How are you doing? The weather sucks right now. How’s work? Busy today?” Establish a connection to your target through commonalities.
  • Establish a pretext – who are you pretending to be? What is your purpose? Establish authority.
  • Emotional injection – this is the start of the RIP phase (reactionary identity preservation). Ask them universal questions like, “Did you use Google today?” “Are you aware that we have an employee handbook? Did you read it?” Note that most of these are yes/no questions and that all of them should have the same response, no matter who you call (thus universal or almost rhetorical). This gets your target into an almost defensive stage and gives them a warm fuzzy feeling when they answer correctly – reassure them that they won the argument and play with their emotions. Get their responses (make them angry, worried, or comfortable).
  • Team up – once you get them to emotionally connect with you on the problem that you have (oh you have a virus infection), work towards getting your goal by teaming up and fixing the problem. “Hey, so let’s just take care of this virus infection quickly” or “let’s figure something out”. Prepare to get your goal.
  • Request an action – have them go to a website, give them their credentials, or open up a command shell and start typing things – whatever your goal is, ask them to do it now. If they refuse, don’t give up and try to obtain your goal some other way (Plan B, C, D…).
  • Closing – Once you accomplish your goal, make them feel like the accomplished something as well. Give them reinforcement saying something like, “You did everything right, good job” or, depending on other emotional responses, give them other feedback like warnings, “Don’t open bad websites” or “let’s just keep this between us; I’d rather not have to tell your boss about this so that you don’t get in trouble.” This is a good phase to make sure that they don’t go and tell others about the incident so that you have more opportunities to contact others at the business.
Posted in Security | No Comments »

VMware Workstation Automatic Suspend/Start and Backup Scripts for Ubuntu

I had previously listed some scripts that I use for VMware Workstation management on the Intrinium blog, but have found a few annoyances with running them. So, I’m re-releasing the scripts in their modified form.

The objective performed by these scripts are as follow:

  • They provide a simple way to suspend all virtual machines on host shutdown and resume only a select few on host startup.
  • They provide a way to check if certain virtual machines are running. If they are not running, the script will send an alert (and attempt to start the machine).
  • Backups will occur on a per-machine basis (that can be set to be different than the running list) and will only suspend one machine at a time for backups, starting them back up once the backup is finished. The backup will be sent to a samba share in my example, but this can be configured to be anything you can cp/scp to.

I’ve also modified the scripts to be a little more flexible in terms of modularity (user set directories) and included some error checks. Other than that, you know the routine – test it before you run it and don’t blame me if you delete everything.

First, the startup script I’ve dropped as /etc/init.d/vmwaresuspend (and of course, run “update-rc.d vmwaresuspend defaults”). This script runs on startup and shutdown of the host, making sure to suspend all running virtual machines and start up only machines on the machine list when the host starts back up:

#!/bin/bash

### BEGIN INIT INFO
# Provides: vmwaresuspend
# Required-Start:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Required-Stop: 0 1 6
### END INIT INFO
USER="media" #the user to run VMware as
MLIST="/media/raid/vmware/machine.list" #the machine list for machines to START on startup (see example below)

case "$1" in

start)
if [ $# -gt 1 ]
then
        su ${USER} -c "vmrun start '$2' nogui"
else
        while read VM;
        do
                echo "Starting $VM"
                su ${USER} -c "vmrun start '$VM' nogui"
        done < $MLIST
fi
exit 0
;;

stop)
if [ $# -gt 1 ]
then
        su ${USER} -c "vmrun suspend '$2'"
else
        vmrun list|grep vmx$|while read VM
        do
                echo "Suspending $VM"
                su ${USER} -c "vmrun suspend '$VM'"
        done
fi
exit 0
;;

restart)
# Nothing to be done for restart
exit 0
;;
esac
exit 0

The machine list is simply the full path to the vmx file for the virtual machine, one per line. Example:

/home/vmware/vm1/vm1.vmx
/home/vmware/vm2/vm2.vmx

Next, the script that checks to see if VMs are running. I set the list to be the same one as the startup script above, but you can change it to a different one if you feel like it. I saved this in /root/cron/checkVMs.sh and updated cron to have it run this script every 5 minutes:

#!/bin/bash
MLIST="/media/raid/vmware/machine.list"
LOCKFILE="/media/raid/vmware/backup.lock"

if [ -f ${LOCKFILE} ]; then #checks for the lock file made by backup script
        exit 0
fi
# vmrun list | tail -n+2 | awk -F/ '{print $NF}' | rev | cut -d\. -f2- | rev ...or we can just ps grep :)
while read VM;
do
        if ! ps ax | grep -v grep | grep "$VM" > /dev/null
        then
                echo "$VM is down!"
                #include mail -s here if you don't receive output for cron jobs.
                #include "vmrun start $VM" if you want to start the VM automatically if down
                # - it might not work due to other factors (rebuilding vmware modules, etc)
        fi
done < $MLIST

Lastly, the backup script. Like with the checking script, you can set a different machine list for it if you are wanting to backup other machines. Do note, this script does try to suspend the machine before backup and then start it after the backup completes – if your machine was stopped and you prefer it to remain stopped after the backup, you will have to change the logic of the script. Otherwise, it will start up. I saved this as /root/cron/backupVMs.sh and set cron to run it every week on Sunday night:

#!/bin/bash
LOCKFILE="/media/raid/vmware/backup.lock"
BACKUP_DIR="/home/media/vmwarebackup"
SMB_DIR="//yourShareServer/vmwarebackup"
MLIST="/media/raid/vmware/machine.list"
SUSPEND="/etc/init.d/vmwaresuspend"
CREDFILE="/root/cron/smbcred"

if [ -f $LOCKFILE ]; then
        echo "A backup is currently in progress"
        exit 1
fi
touch $LOCKFILE

mount -t smbfs -o credentials=$CREDFILE $SMB_DIR $BACKUP_DIR

if mountpoint -q $BACKUP_DIR #did it mount? If not, bad things could happen to your tiny 60GB SSD drive
then
        find $BACKUP_DIR -mtime +30 | xargs rm -rf #remove backups over 30 days old
        datetime=$(date '+%d_%m_%y_%H_%M')
        mkdir $BACKUP_DIR/${datetime}
        cd $BACKUP_DIR/${datetime}
        while read VM;
        do
                mkdir $(basename ${VM})
                ${SUSPEND} stop $VM
                cp -R $(dirname ${VM})/* ./$(basename ${VM})/
                ${SUSPEND} start $VM
        done < $MLIST
        umount -l $BACKUP_DIR
else
        echo "Samba share failed to mount: $BACKUP_DIR"
fi
rm $LOCKFILE

Modify to your hearts content.

Posted in Linux, Programming | No Comments »

Setting up LDAPS to Active Directory in Subsonic on Ubuntu

…using an internal subordinate CA. Ok, so Subsonic is a really awesome music streaming tool that is multi-platform and lets you stream music to all sorts of devices. The really neat thing I found with it is that it supports LDAP authentication, which means I don’t have to recreate user accounts and keep track of different passwords for all the users in my home network – I just leave that task to my domain controller. I’ve ran into a few interesting issues trying to get it setup though, so for the sake of my sanity if I ever need to set it up again, and for those of you out there that may be struggling with the same issues, I’ve decided to write up a little howto guide.

Prerequisites

So before continuing, I am assuming the following about your setup:

  • AD (Active Directory) is currently installed somewhere on your network.
  • LDAPS is enabled for AD (if you set up an Enterprise CA, this process becomes much easier).
  • You have created an account whose sole purpose is to read AD information (the number of times I’ve seen someone use a domain admin account for something like this…is the number of times I’ve owned a network in mere seconds :) )
  • You have a list of users in a group on AD for which you are granting access to subsonic. This can be a group called <insert name here> or simply just any user in the “Users” group.
  • Subsonic is installed on your Ubuntu system and you have a local admin account that you created for it.
  • You installed HTTPS on subsonic. It’s pretty pointless to use LDAPS without also using HTTPS.

Configure the java keystore

LDAPS by nature requires the proper use of certificates, for security reasons of course. In order to verify the certificate from the AD server, the subsonic machine and AD machine must have a common CA that they trust. If you are using an internal CA to issue your LDAPS cert for your AD machine, all it takes is installing the same CA onto subsonic machine. Right? Well, partially.

You see, just like an application like Firefox will have its own individual keystore where it keeps the certificates it trusts, Java does the same thing. So just throwing the cert into /etc/ssl/certs/ won’t do you too much good. Fortunately, Ubuntu makes this process a bit easier.

Make sure you install the ca-certificates-java package (comes preinstalled with openjdk usually):

sudo apt-get install ca-certificates-java

Next, throw your CA cert into /usr/local/share/ca-certificates. Run the following command:

sudo update-ca-certificates

You should get output telling you that at least 1 new certificate has been added. This command will also automatically update your java keystore. You can check to see if your certificate has successfully been imported by running:

keytool -list -v -keystore /etc/ssl/certs/java/cacerts | grep "Your CA Name"

The default password for the java keystore is “changeit” (you may want to change it :) ).

Setting up Subsonic

Log into subsonic. Go to Settings -> Advanced and enable LDAP authentication. The biggest difference between LDAP and LDAPS here is the protocol (ldap:// vs ldaps://) and the port number changed (from 389 to 636). For the LDAP information, you will have to substitute your own information. For me, I have a group of users called “Subsonic” who are allowed access to the application. By default, the LDAP URL that is autofilled for you includes all users in the “Users” group, which is not what I wanted. I had to modify the LDAP search filter to include only users in the Subsonic group (if you want to include all users, you can just leave this alone):

LDAP URL:            ldaps://myADserver:636/cn=Users,dc=yourdomain,dc=com
LDAP search filter:  (&(sAMAccountName={0})(&(objectCategory=user)(memberof=cn=Subsonic,cn=Users,dc=yourdomain,dc=com)))
LDAP manager DN:     yourdomain\limiteduser

To explain further, the search filter looks for a return field of sAMAccountName which is the username of the user in the specific place you are looking. The Base DN starts looking at the root of the domain in the Users group (cn=Users,dc=yourdomain,dc=com) with the search filter of “objectCategory=user” (so any user account, but not computer or group accounts) AND a search filter of “memberof=cn=Subsonic…” which looks for the Subsonic group to be part of the user object in AD. Do note, there is a group object which has a list of users…but this is easier.

LDAP configuration in subsonic

If you are interested with fooling around a bit more with possible search strings and base DN configs, check out JXplorer.

Posted in Linux, Security | No Comments »

Uploading a file by .ajax() (jQuery) with MVC 3 using FormData

In one of my projects, I ran across a scenario in which I needed to upload a file and store it in a database. Sounds simple enough, right? Well, I like to be bit masochistic as it turns out, and I developed a heavy AJAX use application for performance and caching reasons. By default, AJAX calls are not designed and will not include file input types as post data. Everything else, sure, just not files, which kind of sucks when my AJAX heavy application required its use.

Luckily, there were a couple of ways around this. You can either create an iframe which gets passed the information for the upload or you can use the HTML 5 FormData function call. The former does have limitations with callback functionality, however is more widely supported. The latter won’t work if you are using Internet Explorer. Fortunately, since my application will only be used by browsers supporting HTML 5 functionality, including XMLHttpRequest Level 2, it turns into a non-issue, at least for now. You can check and see if your browser supports that functionality as well.

So like regular file uploads, AJAX file uploads using FormData aren’t much different, you have to set up your view file to have a form which supports multipart/formdata encoding and you have to have an input type of file with a name attribute. Pretty simple.

In the view (create):

@model HIDDEN.Models.doc_screenshot

<h2>Create</h2>

@using (Html.BeginForm("Create", "docScreenshot", FormMethod.Post, new { enctype="multipart/form-data"}))
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Add a screenshot</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.name)
            @Html.ValidationMessageFor(model => model.name)
        </div>
        <input name="upimage" class="uploadScreenshot" type="file" />
        @Html.HiddenFor(model => model.lineItemID, "doc_lineItem")

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

The controller remains the same as well. In my project, I am receiving the function parameters as a Model type using JSON (which I enable in my main application by adding the JsonValueProviderFactory), therefore I actually have to find the file request and store it in a temporary variable. I accomplish this using the Request.Files[] list as shown below. Just to alleviate some confusion: I am using a custom Json response class in order to return debug or error information; replace with your own implementation.

In the controller:

        [HttpPost]
        public ActionResult Create(doc_screenshot doc_screenshot)
        {
            JsonResponse res = new JsonResponse(); //I use a custom JsonResponse Class to send back error/debugging info
            HttpPostedFileBase file = Request.Files["upimage"] as HttpPostedFileBase; //the "name" attribute of the file input, in this case "upimage"
            if (file == null)
            {
                res.Status = Status.Error;
                res.Message = "You need to add an actual image.";
                return Json(res);
            }
            Int32 length = file.ContentLength; //get the file length
            byte[] tempImage = new byte[length]; //apply the length to the new variable so we can copy contents
            file.InputStream.Read(tempImage, 0, length); //grab file stream contents and store them in temp variable
            // You will probably want to add some checks here for filetype to make sure that you aren't getting something you don't want
            doc_screenshot.image = tempImage; //copy the image over to the model
            if (ModelState.IsValid)
            {
                res.Message = "Successfully created new screenshot";
                res.Status = Status.Ok;
                db.doc_screenshot.Add(doc_screenshot);
                try
                {
                    db.SaveChanges();
                    res.Id = doc_screenshot.id;
                }
                catch (Exception e)
                {
                    res.Message = e.Message;
                    res.Status = Status.Error;
                }
            }
            else
            {
                res.Message = "Model State error.";
                res.Status = Status.Error;
            }
            return Json(res);
        }

Alright, basics out of the way, now we get to the FormData. In the javascript section of my code, I prevent the form from being submitted when the user hits the “upload” button and instead inject my own AJAX functionality. I detect whether the form that is submitted has enctype of “multipart/form-data” and create a new FormData string if it does. If not, I treat it as a standard AJAX form. Oh and since I’m using the dialog jQuery UI component, I make all calls to the form by using “#dialog form” – replace this with your actual form name in your own implementation.

In the javascript

                        $("#dialog form").submit(function (event) {
                            event.preventDefault();
                            action = $("#dialog form").attr("action");
                            if ($("#dialog form").attr("enctype") == "multipart/form-data") {
                                //this only works in some browsers.
                                //purpose? to submit files over ajax. because screw iframes.
                                //also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
                                dataString = new FormData($("#dialog form").get(0));
                                contentType = false;
                                processData = false;
                            }
                            else {
                                // regular form, do your own thing if you need it
                            }
                            $.ajax({
                                type: "POST",
                                url: action,
                                data: dataString,
                                dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
                                contentType: contentType,
                                processData: processData,
                                success: function (data) {
                                   //BTW, data is one of the worst names you can make for a variable
                                },
                                error: function(jqXHR, textStatus, errorThrown)
                                {
                                    //do your own thing
                                }
                            });
                            $("#dialog").dialog("close");
                        }); //end .submit()

And that should get you rolling with using FormData with your MVC application for uploading files. The last resource I’d recommend if you need an alternative is using the uploadify jQuery plugin.

Posted in Programming | 5 Comments »