[pbctf 2020] Gcombo
One day I spied out my friend accessing some google form to enter his secret combination lock. Afterwards, I kept bothering him about it, and he finally decided to give the link to me. Maybe you can figure out his combo for me and get a tasty flag in return: link
We’re given a google form that’s doing all checking entirely on the client side. Inspecting the source code quickly show us the interesting data used to drive the form interactions: FB_PUBLIC_LOAD_DATA_
.
First of all, we can easily read a password: [766405565,"Password Please",null,0,[[1674649702,null,1,null,[[4,301,["s3cuR3_p1n_id_2_3v3ry0ne"]
, and the final page of the form: ["Congratulations! The flag is pbctf{\u003cdigits you got along the way\u003e_\u003cpassword\u003e}",1,0,0,0]
.
The rest of the information in this data seems to concern about 20 blocks, each containing a “page” of the form containing the 10 digits. While doing a brief manual test, we suddenly arrive at a page that already has a choice marked, we somehow looped back. Google forms allow you to let certain answers direct you to different pages (e.g. so people can skip irrelevant parts of a survey), so our intuition tells us we’re looking at some graph (or an easier structure, but at least it’s a directed graph) over all these 20 pages we can see.
Rather than searching online if we could find the exact structure of this data, we figured out the relevant parts for ourselves. In each entry for a digit, we can see a numerical identifier, that occurs at least twice in the data, some a lot more than that. Interpreting this as the goto-link, we observe that don’t lead directly to what we consider to be the next page itself, but rather some “landing pad” that falls through to the page that directly follows it. Each page except the first has such a landing pad, so at that point, we decide to treat the first page slightly differently and assume it has no incoming gotos.
With this structure identified, we can extract the directed graph, perform a quick BFS, and find the code we need.
import json, collections
data = json.load(open("gdocs"))[1]
G = {}
for i in range(1, len(data), 2):
landing = data[i]
ques = data[i + 1]
if not ques[4][0][1]: continue
G[landing[0]] = {k:v for k, _, v, _, _ in ques[4][0][1]}
goal = [x for y in G.values() for x in y.values() if x not in G.keys()][0]
q = collections.deque([(k, v) for k, _, v, _, _ in data[0][4][0][1]])
vis = set(x[1] for x in q)
while q:
s, cur = q.popleft()
if cur == goal:
print(s)
break
for k,v in G[cur].items():
if v not in vis:
vis.add(v)
q.append((s + k, v))
Flag: pbctf{5812693370_s3cuR3_p1n_id_2_3v3ry0ne}