Using AD to find hosts that aren't in AD - fun with the [IPAddress] construct!

Published: 2019-03-20
Last Updated: 2019-03-20 01:45:16 UTC
by Rob VandenBrink (Version: 1)
3 comment(s)

In many internal assessments or "recon mission" style engagements, you'll need to figure out what all the internal subnets are before you can start assessing that address space for issues, targets or whatever you are looking for in that project.  Or, as I had this week, the request was for enumeration of all the hosts that AREN'T in AD.

In many environments, the DHCP server is quick to find, and you can dump the scopes out Easy as Pi (or pie if you prefer) - we covered this a while back: https://isc.sans.edu/forums/diary/DNS+and+DHCP+Recon+using+Powershell/20995/

But say the DHCP servers are dispersed throughout the environment, it's still pretty common to see DHCP servers in each remote location for instance.  Or if you have subnets that don't have a DHCP scope.  How can you enumerate those remote subnets?

Well, you could certainly hijack the routing protocol and dump the routing table, but that's often a tough thing to get permission for.  

How about from the individual host entries in AD?  The IP address is listed there, and in a lot of shops you can assume a /24 for all subnets.  Or if not, larger subnets will just show up as contiguous /24's in your list if you make that assumption.  How does this look like in Powershell?

Let's start by collecting all the IPv4 addresses in play.  This is a "quick and dirty" collection, and will only collect the "first" adapter if  you have any multi-homed windows windows hosts.  We'll start with our old pal "get-ADComputer"

$addrs = get-adcomputer -filter * -property IPV4Address

Now, there are a ton of ways to get the /24's from this list, but since I'm lazy I'll use the "[IPAddress]" construct in PowerShell.  Let's take one element in the list above, and cast it into the "IPv4Address" type, then look at what's there:

[IPAddress] $addrs[6].IPv4Address


Address            : 873890058
AddressFamily      : InterNetwork
ScopeId            :
IsIPv6Multicast    : False
IsIPv6LinkLocal    : False
IsIPv6SiteLocal    : False
IsIPv6Teredo       : False
IsIPv4MappedToIPv6 : False
IPAddressToString  : 10.129.22.52

Perfect, that "Address" member is a straight numeric representation of the IP, and we can do a binary mask of it with the "-band" operator:
 

[ipaddress] ((([Ipaddress] $pcs.ipv4address[6]).address -band ([Ipaddress] "255.255.255.0").address))


Address            : 1474826
AddressFamily      : InterNetwork
ScopeId            :
IsIPv6Multicast    : False
IsIPv6LinkLocal    : False
IsIPv6SiteLocal    : False
IsIPv6Teredo       : False
IsIPv4MappedToIPv6 : False
IPAddressToString  : 10.129.22.0

That final member, IPAddressToString is the final value we want top use going forward!  Let's add "/24" to that, and dump it to a file.  Cleaned up, with a bit of error checking, our final code looks like:

$pcs = get-adcomputer -filter * -property IPV4Address
$subnets = @()
foreach ($addr in $pcs.ipv4address) {
    if ($addr.length -ne 0) {          # because lots of $pcs will have a null address
        $sub = [ipaddress] ((([ipaddress] $addr).address -band ([Ipaddress] "255.255.255.0").address))
        $subnets += $sub.IPAddressToString + "/24"
        }
    }
$subnets | sort | get-unique > subnets.csv

But what if we wanted just the AD members?  That's easy:

$pcs.ipv4address | sort > AD-hosts.csv


Or just the IP Addresses that are *NOT* AD?  This one is particularly useful in finding things on the network that may have been "flying under the radar" - computers that don't belong to the company for instance, or gear that was purchased by other departments.  Or even gear that was purchased by IT, but never properly inventoried so is now "lost".  Or (perish the thought) malicious hosts!

$targetnets = $subnets | sort  | get-unique
$domainips = $pcs.ipv4address | sort | get-unique
$NonADIPs = @()

foreach ($t in $targetnets) {
    $netbits = $t.Substring(0,($t.length-4))
    for ($hostbits = 1; $hostbits -le 254; $hostbits++) {
        if ( -not $domainips.contains($netbits+$hostbits)) {
            $NonADIPs += ($netbits+$hostbits)
            }
        }
    }
$NonADIPs > non-adips.csv

Next step?  Now that we have these lists, take the file of choice and dump it into nmap.  For instance, using the "nonad-ips.csv" file will scan any hosts that are not AD members.
for a simple ping scan:

nmap --open -sn -iL non-adips.csv -oA non-adips.pingscan

or for a simple tcp port scan (note that this not a scan of all ports):

nmap --open -Pn -iL non-adips.out -oA non-adips.scanned

or, if you are looking for non-firewalled windows hosts that aren't AD members (this is one of the concerns that had me writing this in the first place)

nmap -p445,139 --open -Pn -iL nonadips.out -oA nofw-win-non-ad.scanned

Pretty this up as needed - maybe add "--top-ports n" for the top "n" ports, or "-p0-65535" for all ports, but the defaults give you decent coverage to "see what's out there" fairly quickly.  Or if you're looking for something more specific, maybe non-ad hosts with SMBv1 enabled, run

nmap -p445,139 --open -Pn -iL nonadips.out --script smb-protocols.nse -oA nofw-win-non-ad-smb1.out

Then filter the output for just the problem children with:

type smb1.out.nmap | grep "scan report\|SMBv1"

Of course, you can use the subnets file for a more complete scan (which will include AD members), or you could also use the ad-hosts file to scan only AD members for whatever today's target of interest might be.

Or if you (or your client) is in a hurry, use MASSCAN (don't forget to use that bandwidth limiter!!!).  Play with the rate value a bit so that you end up with decent scan results without saturating any WAN or VPN links.  

Using a faster scanner means that you can maybe also scan the complete tcp port range, depending on your time budget and requirements:

masscan -p0-65535 --rate=1000 -iL non-adips.out -oX scan.out.xml

Note that you can still use --top-ports in masscan, so if you only want to hit the top 1000 ports, use "--top-ports 1000" in your final command.  

Finally, no matter what scanner you use, if you have enough information and enough bandwidth you can usually run multiple scans at different rates, depending on the architecture of the network.

If you're digging a bit deeper, of course you can take those same lists and use them as input to Nessus, OpenVAS or any other tool that you have in your arsenal, or whatever tool, script or playbook you may need to write that day.

Note that in any consulting engagement, time matters!  While your scanner is running, you should be off doing other things, not drinking coffee waiting for that scan to finish.  If this is an internal penetration test, you should be off getting domain admin and will likely have obtained all of your engagement targets by the time the scan finishes.  If this is an assessment, the subnet list will be useful, but most likely your final report will be mostly done by the time the scan wraps up - - or 90%-ish done if you needed those scan results for something specific, or if the scans find something surprising.

Keep in mind that this method will only find subnets that AD knows about directly.  So if you've got subnets that are dedicated to non-AD members - things like IP Phones, scales, shipping printers, scanners and the like (stuff that we call "IoT" these days), those subnets are "ships in the night" to AD.  You might find them using DNS or DHCP recon ( https://isc.sans.edu/diary/DNS+and+DHCP+Recon+using+Powershell/20995 ), or you may need to look at actual routing tables for that (stay tuned for that).

Back to the PowerShell bits, the final scripts above are the ones I've been using for a while, mostly because they took all of 20 minutes to bust out, and they work well and "fast enough" for me, so I never did optimize them further.  I'm sure that there's a one-liner here or there that you could use to make it more efficient - please, use our comment form if you've got some suggestions there!  Or if you've found something spectacular with a portscan that didn't show up in the get-adcomputer list (we all have I'm sure), we're all kinds of interested in that too!!

===============
Rob VandenBrink
Compugen

Keywords: powershell recon
3 comment(s)

Comments

I plead ignorance when it comes to AD. But I did spot something that looked odd to me. Regarding,

[IPAddress] $addrs[6].IPv4Address

Address : 873890058
...
IPAddressToString : 10.129.22.52

The decimal value 873890058 is equivalent to 52*256^3 + 22*256^2 + 129*256 + 10. So why does [IPAddress] return 10.129.22.52 for IPAddressToString? I looked online for help (e.g., docs . microsoft . com/en-us/powershell/module/addsadministration/get-adcomputer?view=win10-ps), but I sort of gave up trying to figure this out, and figured I'd mention it only. Anyone care to tell me what I'm missing?

P.S. This and another recent post lists me as Anonymous. I am 'robv'.
An IPv4 address is transmitted in IP packets in "network order": 4 bytes, the first number of the IPv4 address is the first byte, and so on...

On Intel machines, 32-bit/4-byte integers are stored in "little-endian" order: the least significant byte of the integer is stored first.

IPv4 address 10.129.22.52 stored in network order is 0A811634 (hexadecimal). Reading this value as a 32-bit little-endian integer (a variable type often used in applications), gives: 873890058.
Interesting. And I believe you. But the output from the example violates my sensibilities. I submit that *maybe* it would make more sense if the value labeled "Address" had label "AddressInMemory," "AddressInRAM," etc. But even then if I were to load a 32-bit value from four contiguous bytes into a register, the hardware would know how to decode the four bytes into a value with "10" in the high-order byte (in this example). This register value is the value I think of if someone is going to give me an IP address in decimal format.

As an aside, I have always considered big/little-endian, network byte order, order of bits within a byte (on the wire) as a little mysterious, and it usually doesn't affect me, because whenever I have had a whole IPv4 address in a variable -- a variable in a higher-level language -- it's had its high-order byte being the "10" in this example. (It's been a long time since I programmed in assembler, but I seem to recall AH, AL, etc., and these register values "made sense" to me.)

I tried to find documentation on just what that decimal value "Address" was, but was unsuccessful, and I have too much going on to pursue this. I think if an app is going to edit a 4-byte value into a decimal value, it only makes sense to make sure "endian-ness" does not play a role unless it's clearly documented that's what's going on. In this example, if the "ToString" value was not present, I bet mistakes would be made; perhaps that's why they added the "ToString" value in the first place; I don't know. My two cents. Thanks for reading.

P.S. Regarding the P.S. in my previous message -- it says 'robv' now. I wonder if this one will. Or maybe it takes time. Whatever.

P.P.S. I just ran across whatismyipaddress dot com, and if you ask for "more details," you get a display that shows the IP address and its decimal equivalent. The decimal equivalent on this web site is the one that does *not* violate my sensibilities.

Diary Archives