├── .DS_Store ├── images ├── .DS_Store ├── bigspin │ ├── .DS_Store │ ├── bigspin_1.png │ ├── bigspin_2.png │ ├── bigspin_3.png │ └── bigspin_4.png └── cloudb │ ├── .DS_Store │ ├── cloudb_1.png │ ├── cloudb_2.png │ ├── cloudb_3.png │ ├── cloudb_4.png │ └── cloudb_final.png ├── midnightsun-ctf ├── bigspin.md └── cloudb.md └── nn8ed ├── README.md └── tindermon.rb /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/.DS_Store -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/.DS_Store -------------------------------------------------------------------------------- /images/bigspin/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/bigspin/.DS_Store -------------------------------------------------------------------------------- /images/bigspin/bigspin_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/bigspin/bigspin_1.png -------------------------------------------------------------------------------- /images/bigspin/bigspin_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/bigspin/bigspin_2.png -------------------------------------------------------------------------------- /images/bigspin/bigspin_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/bigspin/bigspin_3.png -------------------------------------------------------------------------------- /images/bigspin/bigspin_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/bigspin/bigspin_4.png -------------------------------------------------------------------------------- /images/cloudb/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/cloudb/.DS_Store -------------------------------------------------------------------------------- /images/cloudb/cloudb_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/cloudb/cloudb_1.png -------------------------------------------------------------------------------- /images/cloudb/cloudb_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/cloudb/cloudb_2.png -------------------------------------------------------------------------------- /images/cloudb/cloudb_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/cloudb/cloudb_3.png -------------------------------------------------------------------------------- /images/cloudb/cloudb_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/cloudb/cloudb_4.png -------------------------------------------------------------------------------- /images/cloudb/cloudb_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreadlocked/ctf-writeups/a88b41a10f0c207bd6113f67b503de8cc20f23b0/images/cloudb/cloudb_final.png -------------------------------------------------------------------------------- /midnightsun-ctf/bigspin.md: -------------------------------------------------------------------------------- 1 | 2 | ## Bigspin (web) 3 | 4 | This weekend, my mates of ID-10-T Team and I decided to play the Midnightsun CTF, we had a long time without playing CTFs so it was nice to meet again and solve some challenges. 5 | 6 | The Bigspin challenge, from web cathegory, has the following statement: 7 | 8 | ``` 9 | This app got hacked due to admin and uberadmin directories being open. Was just about to wget -r it, but then they fixed it :( Can you help me get the files again? 10 | 11 | Service: http://bigspin-01.play.midnightsunctf.se:3123 12 | 13 | Author: avlidienbrunn 14 | ``` 15 | 16 | ### Intro 17 | First is first, let's see what this application looks like on it's root path: 18 | ``` 19 | 20 | What's it gonna be? Are you an 21 | uberadmin, 22 | an admin, 23 | a user, 24 | or (most likely) just a pleb? 25 | 26 | ``` 27 | Ok so we have four kind of "privilege" levels, and probably we need to reach /uberadmin/ path, cool but, How? Trying to just navigate http://bigspin-01.play.midnightsunctf.se:3123/uberadmin/ shows us an Nginx 403 default error, the same occurs with /user, otherwise, /admin shows a 404. How about /pleb?. 28 | 29 | /pleb path returns a 200 OK with the HTML body of http://www.example.com/. 30 | 31 | ![pleb1](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/bigspin/bigspin_1.png) 32 | 33 | Usually, when I face Nginx servers on CTFs and in some real-world cases, I instantly think about Nginx-alias path traversal vulenerabilities, so I tested /pleb../ and.. the server returns 502 Bad Gateway error. 34 | 35 | ![pleb2](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/bigspin/bigspin_2.png) 36 | 37 | Wait, what? If not vulnerable, it should return 404, if vulnerable it should return 404 or 200, as we are asking for an existing file or path. 38 | 39 | ### 1/3 beating user level. 40 | Now we have an unexpected behaviour, when the /pleb/ string is present on the path, the server returns the HTML body of example.com, this could be an indicative of ```proxy_pass``` Nginx directive acting as a reverse proxy to www.example.com, but if we add any characters to the end of /pleb, like /plebidiot, the server returns 502, this means that the server is trying to reach www.example.comidiot, as it can't reach that domain, it returns 502. 41 | 42 | Well, so now we know that some kind of SSRF can be done here, my man @dj.thd told us, what if you use a dynamically resolver dns server based on level1 subdomain, and ignoring other low-level subdomains? Like this www.example.com.127.0.0.1.idiots.com -> 127.0.0.1. Great idea, fortunately for us, there's a service that does exactly this, nip.io. 43 | 44 | So, let's see what happens, when we try to reach /pleb.127.0.0.1.nip.io/user/, 45 | 46 | ![pleb3](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/bigspin/bigspin_3.png) 47 | 48 | Ding ding ding!!! Win!!! We reached /users/ folder, with shows us an Index folder where we can see an "nginx.cönf " file. To read it, my man @patatasfritas used double URL encoding, as Nginx is not that friendly when trying to read files with special characters. 49 | 50 | The nginx.conf file contained the following directives: 51 | ``` 52 | # omited for readability 53 | 54 | http { 55 | 56 | # omited for readability 57 | 58 | server { 59 | listen 80; 60 | 61 | location / { 62 | root /var/www/html/public; 63 | try_files $uri $uri/index.html $uri/ =404; 64 | } 65 | 66 | location /user { 67 | allow 127.0.0.1; 68 | deny all; 69 | autoindex on; 70 | root /var/www/html/; 71 | } 72 | 73 | location /admin { 74 | internal; 75 | autoindex on; 76 | alias /var/www/html/admin/; 77 | } 78 | 79 | location /uberadmin { 80 | allow 0.13.3.7; 81 | deny all; 82 | autoindex on; 83 | alias /var/www/html/uberadmin/; 84 | } 85 | 86 | location ~ /pleb([/a-zA-Z0-9.:%]+) { 87 | proxy_pass http://example.com$1; 88 | } 89 | 90 | access_log /dev/stdout; 91 | error_log /dev/stdout; 92 | } 93 | 94 | } 95 | ``` 96 | 97 | Well, see that? /user path can only be reached on localhost, we aimed that using the location /pleb SSRF, pointing a user controlled domain to 127.0.0.1 using nip.io, a classic SSRF tip. 98 | 99 | ### 2/3 beating admin level. 100 | Now we need to reach ```/admin```, it's configuration is the same, but this time it has an ```internal``` nginx directive, what means internal? Let's Google a bit: 101 | 102 | ``` 103 | Specifies that a given location can only be used for internal requests. 104 | For external requests, the client error 404 (Not Found) is returned. Internal requests are the following: 105 | 106 | - requests redirected by the error_page, index, random_index, and try_files directives; 107 | - requests redirected by the “X-Accel-Redirect” response header field from an upstream server; 108 | - subrequests formed by the include virtual command of the ngx_http_ssi_module module, by the ngx_http_addition_module module directives, 109 | and by auth_request and mirror directives; requests changed by the rewrite directive. 110 | ``` 111 | 112 | Ok, while reading this snippet of documentation, the ```X-Accel-Redirect``` header shines among so many directives. What if we try to do the same as the previous step, but this time resolving to a user controlled server, which always redirects with ```X-Accel-Redirect```pointing to ```/admin```? We can use nip.io again to do this. 113 | 114 | First, we need to setup a simple python web server and configure it to always redirect, simple. Then, we try to reach /pleb.X.X.X.X.nip.io, if everything works, the admin folder should be returned and... 115 | 116 | Ding ding ding!!! Win!!! Another win, now we can reach /admin location. 117 | 118 | ### 3/3 beating uberadmin level. 119 | This level was easy peasy, as when seeing the Nginx configuration file, it highlights the alias traversal on /admin location, so we just need to configure our python server with ```X-Accel-Redirect: /admin..uberadmin/flag.txt``` and... 120 | 121 | ![pleb4](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/bigspin/bigspin_4.png) 122 | 123 | Win!!! We got the flag. 124 | 125 | ### Final thoughts 126 | This challenge was really funny, it's curious how sensitive an Nginx configuration file is. Thanks to @HackingPatatas and @dj.thd for solving this challenge with me. 127 | 128 | Feel free to ping me if you see any mistake at @_dreadlocked on twitter. 129 | -------------------------------------------------------------------------------- /midnightsun-ctf/cloudb.md: -------------------------------------------------------------------------------- 1 | 2 | ## Cloudb (web) 8 solves 3 | 4 | This weekend, my mates of ID-10-T Team and I decided to play the Midnightsun CTF, we had a long time without playing CTFs so it was nice to meet again and solve some challenges. 5 | 6 | The Cloudb challenge, from web cathegory, has the following statement: 7 | 8 | ``` 9 | These guys made a DB in the cloud. Hope it's not a rain cloud... 10 | 11 | Service: [http://cloudb-01.play.midnightsunctf.se](http://cloudb-01.play.midnightsunctf.se/) 12 | 13 | Author: avlidienbrunn 14 | ``` 15 | 16 | ### Intro 17 | Another cloud challenge, I love them! This challenge is about reaching ```admin``` privileges on a Cloud data storage platform. 18 | 19 | ![cloud1](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/cloudb/cloudb_1.png) 20 | 21 | The platform lets you upload a profile pic by directly posting it to an Amazon AWS S3 bucket. The upload process consists on two different requests: 22 | 23 | - A GET to ```/signature``` path, passing an ACL and a HMAC sign as parameters. This request returns a base64 encoded JSON, with an AWS S3 bucket POST policy: 24 | 25 | ![cloud1](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/cloudb/cloudb_3.png) 26 | 27 | The decoded base64: 28 | ```json 29 | { 30 | "expiration": "2019-04-06T14:17:03.000Z", 31 | "conditions": [ 32 | ["content-length-range", 1, 10000], {"bucket": "cloudb-profilepics"}, 33 | {"acl": "public-read"}, 34 | ["starts-with", "$key", "profilepics/"] 35 | ] 36 | } 37 | ``` 38 | 39 | - A POST request to the Amazon AWS S3 bucket using the amazon standard for this kind of requests, explained here: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html 40 | 41 | ![cloud1](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/cloudb/cloudb_4.png) 42 | 43 | Seems like first we are creating a policy, then using this policy to POST our profile pic to the S3 bucket. 44 | 45 | Our profile pic is saved at ```/profilepics/idiotsctf@idiots.con/ejemplo.jpeg```. 46 | 47 | Also, the application lets us request our profile information, at ```/userinfo/idiotsctf@idiots.com/info.json``` endpoint, returning the following JSON: 48 | 49 | ```json 50 | { 51 | "admin": false, 52 | "profilepic": "/profilepics/idiotsctf@idiots.com/ejemplo.jpeg", 53 | "hmac": "427881d9d33e2e619047bbaff90205a6c804524405d3574c8f4dfcabee162788", 54 | "name": "idiotsctf", 55 | "email": "idiotsctf@idiots.com" 56 | } 57 | ``` 58 | 59 | Hmm... that "admin" flag set to ```false```, seems like we will need to overwrite our ```/userinfo/idiotsctf@idiots.com/info.json``` profile file and set the admin flag to true. 60 | 61 | ### Understanding a bit how AWS S3 POST Policy works. 62 | Before continuing, it's necesary to understand a bit how AWS S3 policy works. Amazon let developers upload content via POST requests to a S3 bucket. To do this, the developer needs to define a policy parameter for each user upload, this is what the ```GET /signature``` request does. 63 | 64 | That policy controls how much time the client-side has to upload the content, where it's going to be uploaded, the content type, which bucket is allowed etc. 65 | 66 | The above JSON policy (on Intro), tells: 67 | - Your POST upload permission ends at ```2019-04-06T14:17:03.000Z``` 68 | - You can upload files between 1 and 10000 bytes length. 69 | - You are allowed to upload it to cloudb-profilepics bucket. 70 | - Your ACL will be public-read, so everyone can read the content you upload. 71 | - And the S3 bucket key where you upload the content, must start with ```profilepics/``` 72 | 73 | ### The magic of team work. 74 | Phiber, from int3pids is one of our ID10Ts team colleague, he was the first who started working on this challenge. When I woke up, he had some work done over the challenge as he realized **how to inject characters on the S3 policy** generated by the ```/signature``` controller. 75 | 76 | - First he found the secret key which where used to create the sign on the ```hmac``` parameter. That signed the ```acl``` parameter. The key was ```[object Object]```, literally. 77 | - Second, he was able to inject JSON strings on the ```acl``` parameter. Let's explain it: 78 | 79 | A "common" query, could be ```/signature?hmac=&acl=public-read```, this returns the above mentioned JSON (on Intro). Now, if we inject some strings, like this: ```/signature?hmac=&acl=public-read'} test```, the base64 encoded policy acl looks like this: 80 | 81 | ``` ... {'acl': 'public-read'}, test ...``` 82 | 83 | 84 | We can inject things on the policy!!! 85 | 86 | ### Looking for the buckets. 87 | Now we know that we can tamper the policy, we know we need to overwrite ```/userinfo/idiotsctf@idiots.com/info.json``` file to obtain admin privileges, but we don't know the S3 bucket name where the user information is being stored. 88 | 89 | **Patatas in action:** 90 | Our team mate @HackingPatatas has an incredible ability to try random things and make it work. He tried ```cloud-users.s3.amazonaws.com``` and it was a valid bucket. So it's highly probable that the ```info.json```file is being stored on that bucket. 91 | 92 | ### Bypassing policy restrictions. 93 | The policy tells us we can't just query ```cloud-users``` bucket, as the buckets and the keys of the buckets we are allowed to query, are well defined on the policy. We need to figure out how to invalidate those restrictions and add our own. 94 | 95 | If we try to POST something like this : 96 | ``` 97 | POST https://cloudb-users.s3.amazonaws.com/ 98 | 99 | { 100 | "key":"users/idiotsctf@idiots.com/info.json", 101 | ... 102 | } 103 | ``` 104 | 105 | The AWS S3 server will fail as the policy only allows ```cloud-profilepics``` as bucket and ```profilepics/``` as bucket key path. 106 | 107 | A way to bypass, at least the ```bucket``` restriction is adding another ```conditions``` JSON block. In JSON if the same key appears many times on the same level, the last is going to be the valid one. In this case, as the ```conditions``` directive appears before the user controlled ```acl``` injectable parameter, we can inject something like this to invalidate the previous conditions: 108 | 109 | ``` 110 | public-read'}], 111 | 'conditions': [ 112 | ['starts-with', '$key', 'users/'], 113 | {'bucket':'cloudb-users'}, 114 | {'acl':'public-read 115 | ``` 116 | 117 | See how we are closing the previous ```condition``` with ```]```, and opening a new one, with the ```cloud-users``` as allowed bucket. This returned the error ```Invalid according to Policy: Policy Condition failed: ["starts-with", "$key", "profilepics/"]``` 118 | 119 | Hmmm, our ```starts-with``` directive is being overwritted by the original one, which is after our injection point. 120 | 121 | ### The magic of the patata... again. 122 | We need to find a way to invalidate the second ```starts-with: profilepics/``` directive, we can do this by creating a new JSON block at the end of our injection, like this: 123 | 124 | ``` (our injection) ... ], 'new-block': [{'acl': 'public-read```, so the second ```starts-with``` is not going to be inside the ```conditions``` block anymore. BUUUUUUT, we just can't declare blocks with names other than ```conditions``` or ```expiration``` or we get an error like this ```Invalid Policy: Unexpected: 'new-block'```. 125 | 126 | This is terrible, we can only use two directives, the first one, ```conditions```, will overwrite our injected condition again, so we do not win anything, and the second, ```expiration```, only supports a single string as value. 127 | 128 | After a bit of try, error, try, error, our team mate @HackingPatatas, made it again. He wrotes on team's chat: 129 | ``` 130 | DarkPatatas, [6 abr 2019 15:29:10]: 131 | ostia.. un sec (the hell... wait a second...) 132 | 133 | JAJASDJASJASODJASOJDAOSJDAOSJdas (laughts in spanish) 134 | 135 | ME MATO (I'll kill myself) 136 | 137 | lo tengo (I got it) 138 | ``` 139 | 140 | What? How? 141 | 142 | He just fucking tested ```Conditions``` instead of ```conditions```, and Amazon take it as a valid Policy key, but not applying it as a condition for the policy, so we successfully bypassed al the restrictions. 143 | 144 | The next step is do the same but this time, uploading an ```info.json``` to /users/idiotsctf@idiots.com/info.json, with the "admin" flag set to ```true```. And... 145 | 146 | ![cloud1](https://raw.githubusercontent.com/dreadlocked/ctf-writeups/master/images/cloudb/cloudb_final.png) 147 | 148 | Ding ding ding!!! We are admin!!! And, of course, now we can go to ```/admin``` endpoint, authenticate and read the flag. 149 | 150 | ### Final thoughts 151 | 152 | What the hell Amazon? 153 | 154 | Thanks to all the team, specially @phiber_int3 and @HackingPatatas to help a lot on solving this challenge. It was really funny and challenging! 155 | -------------------------------------------------------------------------------- /nn8ed/README.md: -------------------------------------------------------------------------------- 1 | ## Tindermon (Web) 2 | This weekend, Navaja Negra 8 CTF started, organized by ka0labs.org. This web challenge has the following statement: 3 | 4 | Get the admin password! There is a WAF and it is NodeJS... Easy peasy! 5 | [http://tindermon.ka0labs.org](http://tindermon.ka0labs.org/) 6 | 7 | ### Intro 8 | This challenge presents us a classic NodeJS + Express app. Source code of index is: 9 | ```html 10 | 11 | <-- WebSite Created by the admin pikachu --!> 12 | Welcome to our Pokemon-Tinder!!!!! 13 | 14 |

List of Users Registered in Tindermon

15 |

16 | 17 | 18 |
19 | 20 | ``` 21 | 22 | Two interesting things here: 23 | 24 | - The admin username is pikachu 25 | - There's a route /avatar/```username``` which, when visited, redirects us to /img/``ìd``.jpg where id seems to be the user id. 26 | 27 | ``` 28 | GET /avatar/magikarp HTTP/1.0 29 | 30 | -> 302 Found, Location: /imgs/1.jpg 31 | ``` 32 | 33 | #### Not that easy, there's a "WAF" 34 | Testing some characters shows us that there's some kind of check for the following chars: 35 | ```" ' . (space)``` 36 | Why those characters and not others like > or